[
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n## Bug report for Cloudinary Java SDK\nBefore proceeding, please update to latest version and test if the issue persists\n\n## Describe the bug in a sentence or two.\n…\n\n## Issue Type (Can be multiple)\n[ ] Build - Can’t install or import the SDK\n[ ] Performance - Performance issues\n[ ] Behaviour - Functions aren’t working as expected (Such as generate URL)\n[ ] Documentation - Inconsistency between the docs and behaviour\n[ ] Other (Specify)\n\n## Steps to reproduce\n… if applicable\n\n## Error screenshots or Stack Trace (if applicable)\n…\n\n## Build System\n[ ] Maven\n[ ] Gradle\n[ ] Other (Specify)\n\n## OS (Please specify version)\n[ ] Windows\n[ ] Linux\n[ ] Mac\n[ ] Other (specify)\n\n## Versions and Libraries (fill in the version numbers)\nCloudinary Java SDK version - 0.0.0\nJVM (dev environment) - 0.0.0\nJVM (production environment) - 0.0.0\nMaven - 0.0.0 / N/A\nGradle - 0.0.0 / N/A\n\n## Repository\nIf possible, please provide a link to a reproducible repository that showcases the problem\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "content": "---\nname: Feature request\nabout: Suggest an idea for this project\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n## Feature request for Cloudinary Java SDK\n…(If your feature is for other SDKs, please request them there)\n\n\n## Explain your use case\n… (A high level explanation of why you need this feature)\n\n## Describe the problem you’re trying to solve\n… (A more technical view of what you’d like to accomplish, and how this feature will help you achieve it)\n\n## Do you have a proposed solution?\n… (yes, no? Please elaborate if needed)\n"
  },
  {
    "path": ".github/pull_request_template.md",
    "content": "### Brief Summary of Changes\n<!-- Provide some context as to what was changed, from an implementation standpoint. -->\n\n#### What does this PR address?\n- [ ] GitHub issue (Add reference - #XX)\n- [ ] Refactoring\n- [ ] New feature\n- [ ] Bug fix\n- [ ] Adds more tests\n\n#### Are tests included?\n- [ ] Yes\n- [ ] No\n\n#### Reviewer, please note:\n<!--\nList anything here that the reviewer should pay special attention to. This might\ninclude, for example:\n* Dependence on other PRs\n* Reference to other Cloudinary SDKs\n* Changes that seem arbitrary without further explanations\n-->\n\n#### Checklist:\n<!--- Go over all the following points, and put an `x` in all the boxes that apply. -->\n<!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->\n- [ ] My code follows the code style of this project.\n- [ ] My change requires a change to the documentation.\n- [ ] I ran the full test suite before pushing the changes and all the tests pass.\n"
  },
  {
    "path": ".github/workflows/build.yml",
    "content": "name: Java SDK Matrix CI\n\non:\n  push:\n    branches-ignore:\n      - staging-test\n  pull_request:\n\njobs:\n  build:\n    name: Test ${{ matrix.module }} on JDK ${{ matrix.java }}\n    runs-on: ubuntu-latest\n\n    strategy:\n      matrix:\n        java: ['8']\n        module: [ 'core', 'http5', 'taglib' ]\n\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v4\n\n      - name: Set up JDK ${{ matrix.java }}\n        uses: actions/setup-java@v4\n        with:\n          distribution: 'adopt'\n          java-version: ${{ matrix.java }}\n\n      - name: Clean Gradle plugin cache\n        run: |\n          rm -f  $HOME/.gradle/caches/modules-2/modules-2.lock\n          rm -fr $HOME/.gradle/caches/*/plugin-resolution/\n\n      - name: Cache Gradle\n        uses: actions/cache@v4\n        with:\n          path: |\n            ~/.gradle/caches\n            ~/.gradle/wrapper\n          key: ${{ runner.os }}-gradle-${{ matrix.java }}-${{ matrix.module }}-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}\n          restore-keys: |\n            ${{ runner.os }}-gradle-\n\n      - name: Create test subaccount\n        run: ./gradlew createTestSubAccount -PmoduleName=${{ matrix.module }}\n\n      - name: Load CLOUDINARY_URL and run ciTest\n        run: |\n          source tools/cloudinary_url.txt\n          ./gradlew -DCLOUDINARY_URL=$CLOUDINARY_URL ciTest -p cloudinary-${{ matrix.module }} -i"
  },
  {
    "path": ".gitignore",
    "content": "## Apple storage files\n*.DS_Store\n\n## Android default ignore\n# Built application files\n*.apk\n*.ap_\n\n# Files for the Dalvik VM\n*.dex\n\n# Java class files\n*.class\n\n# Generated files\nbin/\ngen/\n\n# Gradle files\n.gradle/\nbuild/\n/*/build/\n\n# Local configuration file (sdk path, etc)\nlocal.properties\n\n# Proguard folder generated by Eclipse\nproguard/\n\n# Log Files\n*.log\n\ntarget/\ntest-output/\n.settings\n.classpath\n.project\n\n# intellij\n.idea/\n*.iml\n\nappengine-web.xml\ncloudinary-android/src/androidTest/AndroidManifest.xml\n\n##Tools\n/tools/cloudinary_url.txt\n/tools/History.md\n\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "2.3.1 / 2025-08-13\n==================\n\n* Bump dependencies version\n\n2.3.0 / 2025-06-18\n==================\n* Fix API parameters signature\n* Fix build single resource params\n* Add skip backup parameter to delete folder api\n\n2.2.0 / 2025-02-02\n==================\n\n* Fix Uploader strategy\n* Add restore assets by asset ids\n* Add allow dynamic list parameter\n* Add delete resources by asset ids\n\n2.1.0 / 2025-01-20\n==================\n\n* Fix Http client proxy\n* Fix Http client system properties support\n* Add Cloudinary constructor for `Configuration`\n* Fix Register strategy functions\n\n2.0.0 / 2024-09-29\n==================\n\n* Bump minimum Java version to 8\n* Secure true by default\n* Add `auto_chaptering` and `auto_transcription` to upload API\n* New Http client\n* Add support for update metadata field set default disabled\n\n1.39.0 / 2024-07-14\n===================\n\n* Add conditional metadata rules api\n* Fix rename folder endpoint\n* Add config api call\n* Add delete backup asset version support\n* Add rename folder api support\n* Add analyze api\n* Add selective response support\n* Add access key management\n* Add restrictions field to metadata \n\n1.38.0 / 2024-02-18\n===================\n\n* Add `notification_url` support to rename and destroy\n\n1.37.0 / 2024-01-14\n===================\n\n* Update analytics token\n* Add missing display name parameter\n\n1.36.0 / 2023-12-04\n===================\n\n* Fix encode url for fetch layer\n* Add support to use fetch format\n\n1.35.0 / 2023-10-11\n===================\n\n  * Update analytics token\n  * Add support for `on_success` upload parameter\n\n1.34.0 / 2023-08-08\n===================\n\n  * Add visual search support\n  * Add `toUrl() to Search API\n  * Add Search folders functionality\n  * Update Hyper SQL version\n  * Add support for `media_metadata` parameter\n  * Add support for `clear_invalid` parameter\n\n1.33.0 / 2022-09-12\n==================\n\n* Add dynamic folders support\n* Fix VideoTag not appending auth token\n* Fix upload with Unicode character not appending a file extension\n* Bump springboard version\n\n\n1.32.2 / 2022-05-10\n===================\n\n  * Fix nexus publishing script\n\n1.32.1 / 2022-04-25\n===================\n\n  * Fix double underscore handling during normalization\n  * Update Spring framework version\n\n1.32.0 / 2022-04-05\n===================\n\nNew functionality\n-----------------\n  * Add folder decoupling support\n  * Support multiple acls in cookies\n  * Support structured metadata in `resources` api call\n  * Rename API call returns `metadata` and `context` \n  * Support start offset and end offset as expression\n  * Get the details of a single resource by asset_id\n  * Search by asset id\n  * Support metadata fields reordering\nOther changes\n-------------\n  * Fix `verifySignature` timestamp units\n  * Fix transformations API call\n\n1.31.0 / 2022-03-21\n====================\n\nNew functionality\n-----------------\n  * Get resources by asset id\n  * Add `enabled` parameter to `updateUser`, `replaceUser` and `createUser`\n  * Add tags as an array\n  * Add lowercase support for headers in API responses\n  * Allow to disable b-frames\n  * Support download backup version api\n  * Support `filename_override` upload parameter\n  * Add support for single character variable\n\n1.30.0 / 2022-02-02\n===================\n\nOther Changes\n-------------\n  * Update `README.md`\n  * Add feature `SDK analytics`\n  * Fix a bug where a publicId which contains 'v[num]' is considered to contain a version, therefore the version is skipped. (#242)\n\n1.29.0 / 2021-02-10\n===================\n\nNew functionality\n-----------------\n  * Allow setting the user agent (#235)\n  * Add support for Apache http-client 4.5 (#234)\n\nOther changes\n-------------\n  * Fix test name in `ExpressionTest` (#233)\n\n\n1.28.1 / 2021-02-03\n==================\n\n  * Fix `api` reuse bug when calling `cloudinary.search()` (#232)\n\n1.28.0 / 2021-02-01\n==================\n\n  * Add `oauth` support to Admin Api calls. (#230)\n  * Fix connection reuse when using apache-http-client (versions 4.3 and 4.4) (#231)\n\n1.27.0 / 2020-11-16\n===================\n\nNew functionality\n-----------------\n  * Support `type` parameter in `Uploader.updateMetadata()` (#226)\n  * Add `downloadFolder` method (#219)\n  * Add eval upload parameter (#217)\n  * Add support of SHA-256 algorithm in calculation of auth signatures (#215)\n  * Support different radius for each corner  (#212)\n  * Add support for variables in text style. (#225)\n  * Add support for 'accessibility_analysis' parameter (#218)\n  * Support new parameter and modes in `generateSprite()` and `multi()` API cals.\n  * Add support for `date` param in `Api.usage()` (#210)\n\n  \nOther changes\n-------------\n  * Fix named transformation with spaces (#224)\n  * Fix normalize_expression for complex cases (#216)\n  * Detect data URLs with suffix in mime type (#213)\n\n1.26.0 / 2020-05-05\n===================\n\nNew functionality\n-----------------\n\n  * Add variable support to `Transformation.opacity()` (#209)\n  * Add support for restoring deleted datasource entries (#207)\n  * Add support for 32 char SHA-256 URL signatures.\n  * Add support for `pow` operator in expressions (#198)\n  * Add signature checking methods (#193)\n  \nOther changes\n-------------\n\n  * Fix handling of `max_results` and `next_cursor` parameters for folders api (#203)\n  * Fix `normalize_expression` when a keyword is used in a variable name (#205)\n\n1.25.0 / 2020-02-06\n===================\n\nNew functionality\n-----------------\n  * Allow generating archive with multiple resource types (#174)\n  * Add validation for `CLOUDINARY_URL` scheme (#185)\n  * Support create folder API (#188)\n  \nOther changes\n-------------\n  * Fix/provisioning api params (#195)\n  * Encode URLs in API calls (#186)\n  * Improve support for modifying `set` type metadata fields. (#194)\n  * Ignore `URL` in AuthToken generation if `ACL` is provided (#184)\n\n1.24.0 / 2019-09-12\n===================\n\n  * Add support for `cinemagraph_analysis` parameter. (#182)\n  * Rename Account API methods, add convenience overloads. (#181)\n\n1.23.0 / 2019-08-15\n===================\n\nNew functionality\n-----------------\n  * Add account API support (user and cloud management) (#176)\n  * Add structured metadata APIs and entities (#171)\n  * Add duration to conditions in video (#172)\n  * Add support for `live` parameter to Upload Preset (#173)\n  * Add support for folder deletion (#170)\n  * Add support for forcing a version when generating URLs.\n  * Add support for custom pre-functions in transformation (wasm/remote). (#162)\n\nOther changes\n-------------\n  * Fix base64 url validation (accept parameters). (#165)\n  * Fix build script and travis.yml to support more java versions.\n  * Remove test for similarity search (#163)\n\n1.22.1 / 2019-02-13\n===================\n\n  * Fix Java 1.6 support (#161)\n  * Fix eager transformation chaining. (#159)\n\n1.22.0 / 2019-01-22\n===================\n\n  * Add JVM version to user agent (#157)\n  * Add support for range value in `Transformation.fps()` (#155)\n  * Add support for google-storage URLs (`gs://`) in uploads (#154)\n  * Add `quality_analysis` param in upload, explicit and api.resource calls\n  * Add `named` parameter to list-transformations api.\n\n1.21.0 / 2018-11-05\n===================\n\nNew functionality\n-----------------\n  * Add support for font antialiasing and font hinting for text overlays\n\nOther changes\n-------------\n  * Clone configuration in `Url.clone()`\n\n1.20.0 / 2018-10-10\n===================\n\nNew functionality\n-----------------\n\n  * Add support for web assembly and lambda functions in transformations\n\nOther changes\n-------------\n\n  * Improve performance of `url.generate()` method.\n  * Fix url encoding for AuthToken generation\n\n1.19.0 / 2018-07-22\n===================\n\nNew functionality\n-----------------\n\n  * Add support of `auto` value for `start_offset` transformation parameter\n  * Feature/keyframe interval support\n\nOther changes\n-------------\n\n  * Fix content range header in chunked upload (force US locale)\n  * Keep original filename in `uploadLarge` before sending the InputStream\n  * Update gradle for java 7 TLS fix (https://github.com/gradle/gradle/issues/5740)\n  * Fix Api list tags test - verify the list instead of specific tags\n  * Cleanup upload preset from `testGetUploadPreset` (#129)\n  * Add int overload to `TextLayer.letterSpacing()`\n  * Fix responsive breakpoint format field implementation\n  * Separate modules to run on different travis jobs.\n  * Remove `test02Resources` test (broken and unnecessary).\n  * Fix raw convert error message test\n\n1.18.0 / 2018-03-15\n===================\n\nNew functionality\n-----------------\n\n  * Add access control parameter to upload and update calls\n\nOther changes\n-------------\n\n  * Fix authToken generation when using acl\n  * Fix `testOcrUpdate()` test case (#119)\n  * Configure .travis.yml to show more test information.\n  * Verify `testDeleteByToken` takes all original config into account (#116)\n  * Replace `pom.xml` link with `build.gradle` in README.md\n\n1.17.0 / 2017-11-26\n===================\n\n  * Add missing params to `explicit` and `upload` api\n  * Remove Android from Travis configuration and add JDK versions\n\n1.16.0 / 2017-09-26\n===================\n\n  * Change url suffix and root path limitations\n  * Add update_version.sh\n  * Update Readme to point to HTTPS URLs of cloudinary.com\n  * Fix android repository link\n\n1.15.0 / 2017-09-05\n===================\n\nNew functionality\n-----------------\n\n  * Add format field to `ResponsiveBreakpoint`.\n  * The Android SDK has been moved to https://github.com/cloudinary/cloudinary_android\n\nOther changes\n-------------\n\n  * Add badges to README\n  * Create LICENSE\n  * Centralize response handling and respect `returnError` param.\n  * Fix boolean config values in tag generation\n  * Fix project for java8, update cloudinary dependencies.\n\n1.14.0 / 2017-07-20\n===================\n\nNew functionality\n-----------------\n\n  * Add support for uploading remote urls through `Uploader.uploadLarge()`\n  * Support resuming `uploadLarge`\n  * Streaming profile support.\n  * Update TravisCI to explicitly set distribution\n  * Allow deleteByToken to pass through when there's no api secret in config.\n\nOther changes\n-------------\n\n  * Add test for listing transformations with cursor.\n  * Merge branch 'master' into patch-1\n  * Make restore test run in parallel\n  * Set javadoc encoding to UTF-8.\n  * Update gradle to 4.0.1.\n  * Fix test to run in parallel.\n  * Remove use of `DatatypeConverter` which is not supported in Android\n  * Update Cloudinary dependencies version for java sample project.\n  * Merge pull request #84 from elevenfive/master\n    * Close responsestream\n  * Merge pull request #83 from theel0ja/patch-1\n    * Improved formatting of markdown\n\n1.13.0 / 2017-06-12\n===================\n\nNew functionality\n-----------------\n\n  * Add support for `format` in Responsive breakpoints transformation. (#78)\n  * Add `url_suffix` support for private images. (#76)\n  * Add `type` parameter to `Api.publishResource()` (#73)\n  * Add support for fetch overlay/underlay (#69)\n  * Add `deleteByToken` to `Uploader`.\n  * Rename `deleteDerivedResourcesByTransformations` to `deleteDerivedByTransformation`\n  * Fix `deleteStreamProfile` no-options overload.\n  * Add support for *TravisCI* tests\n  * Replace Maven with Gradle as build tool\n\nOther changes\n-------------\n\n  * Parallelize tests.\n\n1.12.0 / 2017-05-01\n===================\n\nNew functionality\n-----------------\n\n  * Add Search API\n\n1.11.0 / 2017-04-25\n===================\n\nNew functionality\n-----------------\n\n  * Add `fps` transformation parameter.\n\n1.10.0 / 2017-04-02\n===================\n\nNew functionality\n-----------------\n\n  * Add upload progress callback for Android\n  * Add support for notification_url param in `Api.update`\n  * Add support for `allowMissing` parameter in archive creation.\n  * Add `videoTag(String source)` overload to `Url`.\n  * Add `deleteDerivedResourcesByTransformations` to Admin Api\n  * Add `ocr` to explicit API\n\nOther changes\n-------------\n\n  * Add `ocr` gravity value tests\n  * Fix ParseException when accessing `Response.rateLimits` with default locale set to non-english.\n  * Add javaDoc for `MultipartUtility.close()`\n  * Merge pull request #46 Close streams in UploaderStrategy \n  * Add javaDoc for `MultipartCallback`\n  * Add `progressCallback` test cases.\n  * Add test for `generate_archive` of raw resources.\n  * Fix `MultipartUtility` - Verify inner stream is closed if an exception is thrown somewhere along the way.\n\n1.9.1 / 2017-03-14\n==================\n\n  * Add expires at to generate archive (#68)\n  * Add `skip_transformation_name` parameter to generate archive. (#67)\n  * Fix variables.\n    * Fix variable regex.\n    * Make Expression.serialize return normalized expression\n    * Fix variable sorting.\n    * Add tests for variable order.\n  * Avoid normalizing negative numbers.\n  * Normalize effect parameter\n  * Remove duplicate quality parameter line.\n\n1.9.0 / 2017-03-08\n==================\n\nNew functionality\n-----------------\n\n  * Support **User defined variables** and **expressions**.\n  * Add `async` parameter to upload params(#63)\n  * Add `expired_at` parameter to private download. (#60)\n  * Add `moderation` parameter in explicit call (#59)\n\nOther changes\n-------------\n\n  * Fix double encoding for commas and slashes in text layers (#66)\n  * Add artistic filter test (#65)\n  * Add gravity-auto test (#64)\n  * Fix `OutOfMemoryError` when uploading large files in android. Fixes #55 (#57)\n  * Fix encoding error in api update resource (#61)\n\n1.8.1 / 2017-02-22\n==================\n\n  * Add support for URL authorization token.\n  * Refactor AuthToken.\n  * Refactor tests for stability\n  * Support nested objects in CLOUDINARY_URL. e.g. foo[bar]=100.\n  * Add maven items to `.gitignore`.\n\n1.8.0 / 2017-02-08\n==================\n\nNew functionality\n-----------------\n\n  * Access mode API\n\nOther changes\n-------------\n\n  * Fix listing direction test.\n  * Refactor `multi` test\n\n1.7.0 / 2017-01-30\n==================\n\nNew functionality\n-----------------\n\n  * Add Akamai token generator\n\nOther changes\n-------------\n\n  * Fix \"multi\" test\n\n1.6.0 / 2017-01-08\n==================\n\n  * Add Search by context API\n\n1.5.0 / 2016-11-19\n==================\n\nNew functionality\n-----------------\n\n  * Add context API\n  * Escape `\\` and `=` in context\n  * Add `removeAllTags` API\n\n1.4.6 / 2016-10-27\n====================================\n\n  * Add streaming profiles API\n\n1.4.5 / 2016-09-16\n====================================\n  * Better handling of missing/unreadable local files\n\n1.4.4 / 2016-09-15\n====================================\n  * Fix issue when uploading URL with \\n\n\n1.4.3 / 2016-09-09\n====================================\n\nNew functionality\n-----------------\n\n  * New Admin API `Publish`.\n  * Support `to_type` in `rename`.\n  * Add `skip_transformation_name` and `expires_at` to archive parameters.\n  * Support Client Hints.\n\nOther changes\n-------------\n\n  * Add deprecation message to `Layer` classes.\n  * Define `MockableTest`\n  * Add static import of `asMap` and `emptyMap`. Suppress deprecation warnings for backward compatibility tests.\n  * Refactor Quality and Width tests.\n  * Update Junit version and add JUnitParams.\n  * Add Hamcrest tests.\n  * Add tests for auto width and original width and height ( `ow`, `oh`) values\n  * Add `timeout`, `connect_timeout` and `connection_request_timeout` to HTTP43 and HTTP44 Api.\n\n1.4.2 / 2016-05-16\n====================================\n\n  * Sent params as entities for PUT, POST\n  * Use \"_method\" with \"delete\" instead of HttpDelete.\n  * Add `next_cursor` to `Api#transformation()`\n  * Update Google App Engine demo\n  * Add script to create unsigned upload preset for the Android test\n  * Use dynamic tag in sprites test\n  * Add SDK_TEST_TAG to all resources being created.\n  * Remove API limits test\n\n1.4.1 / 2016-03-23\n====================================\n  * Rename conditional parameters `faces` and `pages` to `faceCount` and `pageCount`\n\n\n1.4.0 / 2016-03-19\n====================================\n  * Add Condition builder for faces\n  * Modify explicit test - don't use twitter\n  * Modify categorization test result value\n  * Add Conditional Transformations\n  * Cleanup Whitespace \n  * Use variables for public_id's in rename tests\n  * Fix uploadLarge to use X-Unique-Upload-Id instead of updating params. Solves #18\n  * Fix support for non-ascii chars in upload URL\n\n1.3.0 / 2016-01-19\n====================================\n\n  * Add `responsive_breakpoints` paramater\n  * Use `TextLayer` instead of `TextLayerBuilder`. Use `getThis()` instead of `self()`.\n  * Use constant and meaningful name for upload preset. Rearrange imports.\n  * Update SDK versions in Android projects.\n  * Support cloudinary credentials URL that has an API_KEY but no API_SECRET\n  * Remove redundant `deleteConflictingFiles`.\n  * Merge branch 'master' of github.com:cloudinary/cloudinary_java\n  * support createArchive\n  * line spacng support in text overlay\n  * Create separate test class for Layer\n  * Rename Layer classes. Rename self() to getThis() to match the pattern.\n  * Merge branch 'master' of github.com:cloudinary/cloudinary_java\n  * change user agent - remove spaces. stricter layer parameter check. fix underlay method signature\n  * Merge branch 'master' of github.com:cloudinary/cloudinary_java\n  * Fix Android complex filename test\n\ncloudinary-parent-1.2.2 / 2016-01-19\n====================================\n\n  * Fix Android tests\n  * Enable apache http 4.3 strategy\n  * Support easy overlay/underlay construction\n  * Support upload mappings api. add missing restore test\n  * Support the restore api\n  * Normalize user agent\n  * Add invalidate flag to rename and explicit\n  * Support aspect ratio transformation param\n  * Add filename and complex filename test\n  * Fix encoding issues when JVM default encoding is not UTF-8\n  * Revent timeout exception change\n  * Support filename in upload options. close response objects in http44\n  * Update README. Fixes #28\n  * Merge pull request #26 from wagaun/master\n  * Fixing typo on exception\n  * Update README.md\n\ncloudinary-parent-1.2.1 / 2015-06-18\n====================================\n\n  * Disable java8 doclint\n  * Fix references to 1.1.4-SNAPSHOT. Fix wrong URLs in README.md\n  * Fix documentation and imports\n  * Modify exception message to say that Admin API is not supported.\n  * Fix HTML escaping (fixes upload tags)\n  * Allow android unsigned upload without api_key\n  * Fix http44 response closing.\n\ncloudinary-parent-1.2.2 / 2015-10-11\n====================================\n\n  * Support apache http 4.3 strategy\n  * Support easy overlay/underlay construction\n  * Support upload mappings api\n  * Support the restore api\n  * Normalize user agent\n  * Add invalidate flag to rename and explicit\n  * Support aspect ratio transformation param\n  * Fix encoding issues when JVM default encoding is not UTF-8\n  * Support filename in upload options\n  * Close response objects in http44.\n\ncloudinary-parent-1.2.0 / 2015-04-13\n====================================\n\n  * Support httpcomponents 4.4\n  * Support for video tag and transformations\n  * Add video transformation parameters and zoom transformation\n  * Support ftp url upload\n  * Support eager_async in explicit\n  * Fix UTF-8 issues in API\n  * Add support for video tag. refactor Url based tags\n  * Scrub UrlBuilderStrategy\n  * Enable crippled core mode without loading strategies\n  * Move core test to core\n  * Use URLEncoder instead of AbstractUrlBuilderStrategy. \n  * Use upload_chuncked endpoint for upload large\n  * Improved parameter support for upload_large.\n  * support byte[] file input for upload\n\ncloudinary-parent-1.1.3 / 2015-02-24\n====================================\n\n  * Fix test after file name change\n  * Added timeout parameter to admin api and Fixed test and configuration issues\n\ncloudinary-parent-1.1.2 / 2015-01-15\n====================================\n\n  * Fix support for string eager parameters e.g. for safe mobile flow\n  * Merge pull request #17 from cloudinary/eager_upload_params\n  * merged android signature fix\n  * eager upload params can be both string or List<Transformation>\n\ncloudinary-parent-1.1.1 / 2014-12-22\n====================================\n\n  * Support secure domain sharding\n  * Don't sign version component\n  * Support url suffix and use root path\n    * renamed urlSuffix to suffix\n  * Support tags in upload large.\n  * Change log and version update\n  * added new options to url tag\n  * added invalidate to bulk deletes\n  * Add missing tests in adnroid-test. fix signing tests in android-test. be more specific with exception class in http42 Cloudinary tests.\n  * updated Url.generate method (b4 tests)\n  * bug fixes\n\ncloudinary-parent-1.1.0 / 2014-11-18\n====================================\n\n  * Merge branch 'globalize' of github.com:codeinvain/cloudinary_java\n  * Remove redundant depndencies\n  * - changed org.json to org.cloudinary.json due to Android optimization issues . - removed dependency on SimpleJSON from tablib\n  * Update CHANGES.txt\n  * Merge branch 'globalize'\n  * Fix documentation. Fix dependencies\n  * Fix modules artifactId\n  * promoted minor version (1.0.x -> 1.1.x) & fixed documentation external links\n  * added deprecated asMap method to Cloudinary (support old api)\n  * updated documentation , fixed sample projects\n  * promoted minor version (1.0.x -> 1.1.x) & fixed documentation external links\n  * added deprecated asMap method to Cloudinary (support old api)\n  * updated documentation , fixed sample projects\n  * add support for signed urls in tag helpers (image and url)\n  * Git ignore cloudinary-android-test/src/main/AndroidManifest.xml. Fix tag lib dependency\n  * Remove httpclient dependencies from cloudinary-core. Use main version in both http42 and android versions. Remove getRawResponse from ApiResponse\n  * Merge branch 'globalize' of github.com:codeinvain/cloudinary_java\n  * merged config & builder\n  * Update README.md\n  * cloudinary credentials removed\n  * http42 + android tests pass\n  * changed architecture to core + strategies\n  * removed shared classes\n  * android jar\n  * maven build , project dependency core -> http42 -> taglib\n  * unified Java API and created basic implementation\n  * custom StringUtils\n  * support folder listing API\n\ncloudinary-parent-1.0.14 / 2014-07-29\n=====================================\n\n  * Add background_removal\n  * Support return_delete_token in upload/update params\n  * Support responsive and hidpi\n  * Support custom coordinates.\n\ncloudinary-parent-1.0.13 / 2014-04-29\n=====================================\n\n  * Add support for opacity\n  * Support upload_presets\n  * Support unsigned uploads\n  * Support start_at for resource listing\n  * Support phash for upload and resource details\n  * Support rate limit header in Api calls\n  * Initial commit Google App engine sample\n  * Merge remote master\n  * Allow passing ClientConnectionManager\n\ncloudinary-parent-1.0.12 / 2014-03-04\n=====================================\n\n  * Increment version to 1.0.12\n  * Fix uploader API calls handling of non-string parameters e.g. Booleans\n\ncloudinary-parent-1.0.11 / 2014-03-04\n=====================================\n\n  * Document releases in CHANGES.txt\n  * Fix test - raw upload parts must be > 5m\n  * better large raw upload support\n  * Merge branch 'master' of https://github.com/cloudinary/cloudinary_java\n  * new update method\n  * Add listing by moderation kind and status\n  * Add moderation status in listing\n  * Add moderation flag in upload\n  * Add moderation_status in update\n  * Add ocr, raw_conversion, categorization, detection, similarity_search and auto_tagging parameters in update and upload\n  * Add support for uploading large raw files\n\ncloudinary-parent-1.0.10 / 2014-01-27\n=====================================\n\n  * add discard_original_filename upload flag. Formatting in tests\n  * support setting context in explicit\n  * Add direction support to resource listing.\n\ncloudinary-parent-1.0.9 / 2014-01-10\n====================================\n\n  * remove delete_all from tests. fix face coordinates in explicit\n  * Merge branch 'master' of https://github.com/cloudinary/cloudinary_java\n  * add user agent. fix api test\n  * refactor Map encoding for upload\n  * Merge branch 'signedurl'\n  * Update README.md\n  * Merge branch 'master' of https://github.com/cloudinary/cloudinary_java\n  * support multiple face coordinates in upload and explicit. optionaly use Coordinates as a wrapper of multiple rectangles\n  * add support for overwrite in taglib\n  * add support for overwrite boolean in upload\n  * support signed urls\n  * delete all + cursors, tag and context flags in lists, list by public ids, add support in upload for: face_coordinates, alowed_formats, context\n  * change dependency to published 1.0.8 and change installation instructions accordingly\n\ncloudinary-parent-1.0.8 / 2013-12-20\n====================================\n\n  * Fix implementation of SmartUrlEncoder in case of non-ascii characters\n  * fix callback when servlet is not at root\n  * better handling of raw files\n  * add subsections in README. Add this to memeber assignments\n  * move most of stored file logic to core. support stored file in url and url and image tags. add a readme to the sample project\n  * add support for named transformations as tag attribute\n  * add support for local secure (and implicit from request)  and cdn_subdomain\n  * cleanup and upload parameters completeness\n  * change images to use inline transformations when possible. fix image link in list\n  * fix inline transformation in image. add inline trnaformation in url\n  * initial commit of photo album sample. added additional or modified existing tag helpers to taglib to enable more robust transformations and to allow cloufdinary URLs outside of images and to allow specifying images from facebook/twitter and support jQuery direct upload.\n\ncloudinary-parent-1.0.7 / 2013-11-02\n====================================\n\n  * Support the color parameter\n  * Merge branch 'master' of https://github.com/cloudinary/cloudinary_java\n  * add support for unique_filename and added a test for use_filename\n  * Merge pull request #9 from AssuredLabor/transformationAttr\n  * add transformation attribute to cloudinary upload tag\n  * Fix handling of boolean parameters on upload\n\ncloudinary-parent-1.0.6 / 2013-08-07\n====================================\n\n  * Rename prepareUploadTagParams to uploadTagParams\n  * Escape all public_ids including non-http ones.\n  * Merge pull request #7 from AssuredLabor/extractUploadParams\n  * Updated so we don't escapeHTML unless necessary for the server side. This allows the client-side to receive a JS hash / object directly. This is useful, depending on how the input is rendered.\n  * Extracted upload tagParams and upload url functionality into their functions, this will facilitate frameworks like Angular fetching the server-side params\n\ncloudinary-parent-1.0.5 / 2013-07-31\n====================================\n\n  * Support folder and proxy upload parameters\n  * Fix string comparison of secureDistribution\n  * Change secure urls to use *res.cloudinary.com\n  * Support Admin API ping\n  * Support generateSpriteCss\n\ncloudinary-parent-1.0.4 / 2013-07-15\n====================================\n\n  * Issue #6 - add instructions on using as a maven dependency\n  * Support raw data URI\n  * Support zipDownload. Cleanup signing code\n  * Support s3 and data:uri urls\n\ncloudinary-parent-1.0.3 / 2013-06-04\n====================================\n\n  * Cleanup pom.xml, Fix imageUploadTag test, Fix imports\n  * Introduced a new image tag for jsps, you can use it like this:\n  * don't track eclipse resources\n  * Add the callback and the signature to the image tag\n  * In the tag lib, use the Uploader's tag generator * Allow null file parameters\n  * enhancements to the HTML processing\n  * cleaned up the tag rendering. There is some more flexibility that needs to be added to the tag, but it looks like the core of it is working ok.\n  * correctly located the cloudinary tld and updated to use the new classname of the tag Added a singleton manager to ease spring support.\n  * renamed tag to make more sense\n  * First pass at an upload tag and support code\n  * Refactored Cloudinary Java into multiple modules without breaking the module naming convention already established. * Created a -taglib module to support constructing file input tags on the server side, since it requires some server side API signing. * Separate modules allow users who are writing stand-alone applications (not depending on the Servlet API) not to have a dependency on it.\n  * Fixing code sample, referencing Android\n\ncloudinary-1.0.2 / 2013-04-08\n=============================\n\n  * Upgrade version to 1.0.2-SNAPSHOT\n  * Don't fail api tests if api_secret is not given\n  * Don't fail api tests if api_secret is not given\n  * pom fixes\n  * Preparation for Maven repository submission\n  * Merge Maven preperation by shakiba\n  * Missing file for rename test\n  * Invalidate flags in upload and destroy\n  * Private download link generator\n  * Support for short urls for image/upload\n  * Support for folders\n  * Support rename\n  * Support unsafe transformation update\n  * Fix tags api support of multiple public ids\n  * ready for maven central\n  * Fixing URLs in readme\n  * Support akamai\n  * Support for sprite genreation, multi and explode. Support new async/notification flags\n  * Merge git://github.com/andershedstrom/cloudinary_java\n  * Support for usage API call\n  * Support image_metadata flag in upload and API\n  * Update README.md\n  * fixed regexp bug, regexp didn't work\n  * Updated pom.xml to handle custom src and test-src directories\n  * Allow giving pages flag to resource details API\n  * Fix check for limit. Fix htmlWidth visibility\n  * Support for info flags in upload\n  * Support for transformation flags\n  * Support deleteResourcesByTag Support keep_original in resource deletion\n  * Uploader.imageUploadTag - helper for create input tag for direct upload to Cloudinary via JS\n  * Added README\n  * Java naming conventions. Map utility methods\n  * Initial commit\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2017 Cloudinary\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "MAVEN_CENTRAL_PUBLISHING_GUIDE.md",
    "content": "# Maven Central Publishing Guide - Cloudinary Java SDK\n\nThis guide documents the complete process for publishing the Cloudinary Java SDK to Maven Central using the new Central Portal (central.sonatype.com), replacing the deprecated OSSRH system.\n\n## 🎯 **Overview**\n\n- **Old System:** `oss.sonatype.org` (dead, returns 401 errors)\n- **New System:** `central.sonatype.com` with manual bundle upload\n- **Method:** Manual bundle creation and upload (not automated plugin publishing)\n- **Requirements:** Complete artifacts with checksums and GPG signatures\n- **Current Version:** 2.3.1 → Next version (e.g., 2.3.2)\n\n## 📋 **Prerequisites**\n\n1. **Credentials:**\n   - `centralUsername` and `centralPassword` for central.sonatype.com\n   - Legacy `ossrhToken` and `ossrhTokenPassword` (if available)\n\n2. **GPG Setup:**\n   - GPG key imported: `6B42474E50D0D89A01B40AC225FE63F85DCB788F`\n   - Private key available in repository: `private-key.asc`\n   - Password: `nwov0aaStnO4`\n\n3. **Java Version:**\n   - **Java 8+** (current project targets Java 8)\n   - Verify with: `java -version`\n\n## 🔧 **Configuration Changes Required**\n\n### 1. Update Root `build.gradle`\n\n```gradle\nplugins {\n    id 'maven-publish'\n    // Remove the old nexus plugin: id 'io.github.gradle-nexus.publish-plugin' version '1.0.0'\n}\n\nallprojects {\n    repositories {\n        mavenCentral()\n    }\n    project.ext.set(\"publishGroupId\", group)\n}\n\n// Remove the old nexusPublishing block - we'll create bundles manually for Central Portal\n\ntasks.create('createTestSubAccount') {\n    doFirst {\n        println(\"Task createTestSubAccount called with module $moduleName\")\n        def cloudinaryUrl = \"\"\n        \n        // core does not use test clouds, skip (keep empty file for a more readable generic travis test script)\n        if (moduleName != \"core\") {\n            println \"Creating test cloud...\"\n            def baseUrl = new URL('https://sub-account-testing.cloudinary.com/create_sub_account')\n            def connection = baseUrl.openConnection()\n            connection.with {\n                doOutput = true\n                requestMethod = 'POST'\n                def json = new JsonSlurper().parseText(content.text)\n                def cloud = json[\"payload\"][\"cloudName\"]\n                def key = json[\"payload\"][\"cloudApiKey\"]\n                def secret = json[\"payload\"][\"cloudApiSecret\"]\n                cloudinaryUrl = \"CLOUDINARY_URL=cloudinary://$key:$secret@$cloud\"\n            }\n        }\n\n        def dir = new File(\"${projectDir.path}${File.separator}tools\")\n        dir.mkdir()\n        def file = new File(dir, \"cloudinary_url.txt\")\n        file.createNewFile()\n        file.text = cloudinaryUrl\n\n        println(\"Test sub-account created successfully!\")\n    }\n}\n```\n\n### 2. Create New `publish.gradle` for Modules\n\n```gradle\napply plugin: 'maven-publish'\napply plugin: 'signing'\n\n// Simple module-level publishing for manual upload to Central Portal\nif (hasProperty(\"ossrhTokenPassword\") || hasProperty(\"centralPassword\")) {\n    \n    publishing {\n        publications {\n            mavenJava(MavenPublication) {\n                // Set coordinates from gradle.properties\n                groupId = project.ext.publishGroupId\n                artifactId = project.name\n                version = project.version\n                \n                // Include JAR artifacts and components for Java\n                from components.java\n                artifact sourcesJar\n                artifact javadocJar\n                \n                pom {\n                    name = getModuleName(project.name)\n                    packaging = 'jar'\n                    description = publishDescription\n                    url = githubUrl\n                    \n                    licenses {\n                        license {\n                            name = licenseName\n                            url = licenseUrl\n                        }\n                    }\n                    \n                    developers {\n                        developer {\n                            id = developerId\n                            name = developerName\n                            email = developerEmail\n                        }\n                    }\n                    \n                    scm {\n                        connection = scmConnection\n                        developerConnection = scmDeveloperConnection\n                        url = scmUrl\n                    }\n                }\n            }\n        }\n    }\n    \n    signing {\n        // Configure GPG signing\n        useGpgCmd()\n        sign publishing.publications.mavenJava\n    }\n}\n\n// Helper function to get proper module names\ndef getModuleName(artifactId) {\n    switch(artifactId) {\n        case 'cloudinary-core':\n            return 'Cloudinary Core Library'\n        case 'cloudinary-http5':\n            return 'Cloudinary Apache HTTP 5 Library'\n        case 'cloudinary-taglib':\n            return 'Cloudinary Taglib Library'\n        case 'cloudinary-test-common':\n            return 'Cloudinary Test Common Library'\n        default:\n            return 'Cloudinary Java Library'\n    }\n}\n```\n\n### 3. Update Module `build.gradle` Files\n\nFor each module (cloudinary-core, cloudinary-http5, cloudinary-taglib, cloudinary-test-common), replace the publishing section:\n\n```gradle\nplugins {\n    id 'java-library'\n    // Remove: id 'signing'\n    // Remove: id 'maven-publish' \n    // Remove: id 'io.codearte.nexus-staging' version '0.21.1'\n}\n\napply from: \"../java_shared.gradle\"\napply from: \"../publish.gradle\"  // Apply our new simplified publishing\n\n// Remove the entire old publishing block with nexusStaging\n// The new publish.gradle handles everything\n```\n\n### 4. Update `gradle.properties`\n\n```properties\n# Update URLs to point to new system (for documentation)\npublishRepo=https://central.sonatype.com/\nsnapshotRepo=https://central.sonatype.com/\npublishDescription=Cloudinary is a cloud service that offers a solution to a web application's entire image management pipeline. Upload images to the cloud. Automatically perform smart image resizing, cropping and conversion without installing any complex software. Integrate Facebook or Twitter profile image extraction in a snap, in any dimension and style to match your website's graphics requirements. Images are seamlessly delivered through a fast CDN, and much much more. This Java library allows to easily integrate with Cloudinary in Java applications.\ngithubUrl=http://github.com/cloudinary/cloudinary_java\nscmConnection=scm:git:git://github.com/cloudinary/cloudinary_java.git\nscmDeveloperConnection=scm:git:git@github.com:cloudinary/cloudinary_java.git\nscmUrl=http://github.com/cloudinary/cloudinary_java\nlicenseName=MIT\nlicenseUrl=http://opensource.org/licenses/MIT\ndeveloperId=cloudinary\ndeveloperName=Cloudinary\ndeveloperEmail=info@cloudinary.com\n\n# Update version for next release\ngroup=com.cloudinary\nversion=2.3.2\n\ngnsp.disableApplyOnlyOnRootProjectEnforcement=true\n\n# see https://github.com/gradle/gradle/issues/11308\nsystemProp.org.gradle.internal.publish.checksums.insecure=true\n```\n\n## 🚀 **Step-by-Step Publishing Process**\n\n### Step 1: Environment Setup\n\n```bash\n# Navigate to project\ncd /Users/adimizrahi/Development/Java/cloudinary_java\n\n# Verify Java version (should be Java 8+)\njava -version\njavac -version\n\n# Set GPG environment for batch signing\nexport GPG_TTY=$(tty)\n```\n\n### Step 2: Clean and Build All Artifacts\n\n```bash\n# Clean previous builds and generate all artifacts\n./gradlew clean publishToMavenLocal\n```\n\n**Expected Output:** \n- JAR files for each module (cloudinary-core, cloudinary-http5, cloudinary-taglib, cloudinary-test-common)\n- Sources JARs (`-sources.jar`)\n- Javadoc JARs (`-javadoc.jar`)  \n- POM files with correct XML structure\n- All artifacts signed with GPG (`.asc` files)\n\n### Step 3: Verify Artifacts Generated\n\n```bash\n# Check that all 4 modules have complete artifacts (should be 7 files each)\nfor module in ~/.m2/repository/com/cloudinary/cloudinary-*; do\n    if [[ -d \"$module\" ]]; then\n        echo \"--- $(basename $module) ---\"\n        ls -1 $module/2.3.2/ 2>/dev/null | grep -E \"\\.(jar|pom|asc)$\" | wc -l\n    fi\ndone\n```\n\n**Expected:** Each module should show `7` files:\n- `cloudinary-module-2.3.2.jar` + `.asc`\n- `cloudinary-module-2.3.2-sources.jar` + `.asc` \n- `cloudinary-module-2.3.2-javadoc.jar` + `.asc`\n- `cloudinary-module-2.3.2.pom` + `.asc`\n\n### Step 4: Verify POM Files Are Valid\n\n```bash\n# Check that POM files have proper metadata\nfor pom in ~/.m2/repository/com/cloudinary/cloudinary-*/2.3.2/*.pom; do\n    if [[ -f \"$pom\" ]]; then\n        echo \"--- $(basename $pom) ---\"\n        echo \"Name tags: $(grep -c \"<name>\" \"$pom\")\"\n        echo \"Description: $(grep -c \"<description>\" \"$pom\")\"\n        echo \"License: $(grep -c \"<license>\" \"$pom\")\"\n        echo \"Developer: $(grep -c \"<developer>\" \"$pom\")\"\n        echo \"SCM: $(grep -c \"<scm>\" \"$pom\")\"\n    fi\ndone\n```\n\n**Expected:** Each POM should have all required metadata elements.\n\n### Step 5: Generate Additional Checksums\n\n```bash\ncd ~/.m2/repository\n\n# Generate MD5 and SHA1 checksums for all artifacts (Central Portal requires these)\nfind com/cloudinary/cloudinary-* -name \"*.jar\" -o -name \"*.pom\" | while read file; do\n    if [[ -f \"$file\" ]]; then\n        echo \"Processing $file\"\n        md5sum \"$file\" | awk '{print $1}' > \"$file.md5\"\n        sha1sum \"$file\" | awk '{print $1}' > \"$file.sha1\"\n    fi\ndone\n```\n\n### Step 6: Verify Complete File Set\n\n```bash\ncd ~/.m2/repository\n\necho \"=== FINAL FILE COUNT CHECK ===\"\necho \"JAR/POM files:\" && find com/cloudinary/cloudinary-* -name \"*.jar\" -o -name \"*.pom\" | wc -l\necho \"GPG signatures:\" && find com/cloudinary/cloudinary-* -name \"*.asc\" | wc -l\necho \"MD5 checksums:\" && find com/cloudinary/cloudinary-* -name \"*.md5\" | wc -l  \necho \"SHA1 checksums:\" && find com/cloudinary/cloudinary-* -name \"*.sha1\" | wc -l\n```\n\n**Expected File Count:**\n- 4 modules × 4 artifacts each = **16 original files**\n- **16 GPG signatures** (`.asc`)\n- **16 MD5 checksums** (`.md5`)\n- **16 SHA1 checksums** (`.sha1`)\n- **Total: 64 files**\n\n### Step 7: Create Final Bundle\n\n```bash\ncd ~/.m2/repository\n\n# Create the complete bundle for Central Portal upload\nBUNDLE_NAME=\"cloudinary-java-$(grep '^version=' ~/Development/Java/cloudinary_java/gradle.properties | cut -d'=' -f2)-bundle-COMPLETE.tar.gz\"\n\ntar -czf ~/\"$BUNDLE_NAME\" \\\n$(find com/cloudinary/cloudinary-* \\\n  -name \"*.pom\" -o -name \"*.jar\" \\\n  -o -name \"*.md5\" -o -name \"*.sha1\" -o -name \"*.asc\" | \\\n  grep -v maven-metadata | sort)\n```\n\n### Step 8: Verify Final Bundle\n\n```bash\ncd ~/\n\n# Check bundle size and contents\nls -lh cloudinary-java-*-bundle-COMPLETE.tar.gz\necho \"--- File count ---\"\ntar -tzf cloudinary-java-*-bundle-COMPLETE.tar.gz | wc -l\necho \"--- Sample contents ---\" \ntar -tzf cloudinary-java-*-bundle-COMPLETE.tar.gz | head -16\necho \"--- Module breakdown ---\"\ntar -tzf cloudinary-java-*-bundle-COMPLETE.tar.gz | grep -E \"(core|http5|taglib|test-common)\" | cut -d'/' -f3 | sort | uniq -c\n```\n\n**Expected:**\n- **Size:** ~1-2MB (smaller than Android due to fewer dependencies)\n- **Files:** 64 total\n- **Modules:** 4 modules with 16 files each\n- **Contents:** Each module should have JARs, POMs, and all checksums/signatures\n\n## 📤 **Upload to Central Portal**\n\n### Manual Upload Process\n\n1. **Login:** Go to https://central.sonatype.com/\n2. **Credentials:** Use `centralUsername` and `centralPassword`\n3. **Upload:** Navigate to \"Upload Component\" or \"Publish\"\n4. **Bundle:** Select the `.tar.gz` file created in Step 7\n5. **Publishing Type:** Choose \"USER_MANAGED\"\n6. **Publication Name:** \"Cloudinary Java SDK v{version}\"\n\n### Expected Validation\n\nThe Central Portal will validate:\n- ✅ **POM structure** (proper XML with required metadata)\n- ✅ **Artifact integrity** (MD5/SHA1 checksums match)\n- ✅ **Signatures** (GPG signatures valid)\n- ✅ **Completeness** (all required files present)\n- ✅ **Java compatibility** (JAR files are valid)\n\n## 🛠 **Troubleshooting**\n\n### Common Issues & Solutions\n\n1. **GPG Signing Issues:**\n   - **Cause:** TTY or batch mode problems\n   - **Solution:** `export GPG_TTY=$(tty)` and use `--batch --yes` flags\n   - **Alternative:** Use `signing { useGpgCmd() }` in Gradle\n\n2. **Missing Dependencies in POM:**\n   - **Cause:** Gradle not including transitive dependencies\n   - **Solution:** Verify `from components.java` includes dependencies\n   - **Check:** Examine generated POM files for `<dependencies>` section\n\n3. **Version Conflicts:**\n   - **Cause:** Old artifacts in local repository\n   - **Solution:** `./gradlew clean` and delete `~/.m2/repository/com/cloudinary/`\n\n4. **Module Configuration Issues:**\n   - **Cause:** Inconsistent `build.gradle` files between modules\n   - **Solution:** Ensure all modules apply `publish.gradle` consistently\n\n5. **Bundle Upload Failures:**\n   - **Cause:** Missing or corrupted files in bundle\n   - **Solution:** Verify all 64 files present and re-create bundle\n\n## 📋 **Module-Specific Information**\n\n### Cloudinary Core (`cloudinary-core`)\n- **Artifact ID:** `cloudinary-core`\n- **Description:** Core Cloudinary functionality\n- **Dependencies:** Minimal (mostly standard Java libraries)\n\n### Cloudinary HTTP5 (`cloudinary-http5`)\n- **Artifact ID:** `cloudinary-http5`\n- **Description:** Apache HTTP Client 5 implementation\n- **Dependencies:** `cloudinary-core`, Apache HTTP Components\n\n### Cloudinary Taglib (`cloudinary-taglib`)\n- **Artifact ID:** `cloudinary-taglib`\n- **Description:** JSP Taglib for Cloudinary\n- **Dependencies:** `cloudinary-core`, Servlet API\n\n### Cloudinary Test Common (`cloudinary-test-common`)\n- **Artifact ID:** `cloudinary-test-common`  \n- **Description:** Shared test utilities\n- **Dependencies:** `cloudinary-core`, JUnit, test frameworks\n\n## 📝 **Version Update Checklist**\n\nFor publishing a new version:\n\n- [ ] Update `version` in `gradle.properties`\n- [ ] Update this guide with new version number\n- [ ] Run complete publishing process (Steps 1-8)\n- [ ] Verify all 64 files in final bundle (4 modules × 16 files)\n- [ ] Upload to Central Portal\n- [ ] Verify publication appears on Maven Central\n- [ ] Update GitHub releases and tags\n- [ ] Test artifacts can be consumed by dependent projects\n\n## 🔗 **References**\n\n- **Central Portal:** https://central.sonatype.com/\n- **Migration Guide:** https://central.sonatype.org/publish/publish-guide/\n- **Gradle Publishing:** https://docs.gradle.org/current/userguide/publishing_maven.html\n\n---\n\n**Last Updated:** [Current Date]  \n**Tested Version:** 2.3.2  \n**Success Rate:** ✅ To be tested with this process \n\n## 🚨 **Key Differences from Android SDK**\n\n1. **No AAR files** - Uses JAR files instead\n2. **Java components** - Uses `components.java` instead of `components.release`\n3. **Simpler setup** - No Android-specific build tools required\n4. **Standard Maven structure** - Follows typical Java library patterns\n5. **Fewer files per module** - 16 files per module vs 24 for Android modules\n"
  },
  {
    "path": "README.md",
    "content": "[![Build Status](https://travis-ci.org/cloudinary/cloudinary_java.svg?branch=master)](https://travis-ci.org/cloudinary/cloudinary_java)\n\nCloudinary\n==========\n\n## About\nThe Cloudinary Java SDK allows you to quickly and easily integrate your application with Cloudinary.\nEffortlessly optimize and transform your cloud's assets.\n\n### Additional documentation\nThis Readme provides basic installation and usage information.\nFor the complete documentation, see the [Java SDK Guide](https://cloudinary.com/documentation/java_integration).\n\n## Table of Contents\n- [Key Features](#key-features)\n- [Version Support](#Version-Support)\n- [Installation](#installation)\n- [Usage](#usage)\n    - [Setup](#Setup)\n    - [Transform and Optimize Assets](#Transform-and-Optimize-Assets)\n    - [File upload](#File-upload)\n\n## Key Features\n- [Transform](https://cloudinary.com/documentation/java_video_manipulation) and [optimize](https://cloudinary.com/documentation/java_image_manipulation#image_optimizations) assets (links to docs).\n- [Upload assets to cloud](https://cloudinary.com/documentation/java_image_and_video_upload)\n\n## Version Support\n| SDK Version    | Java 6+ | Java 8 |\n|----------------|---------|--------|\n| 1.1.0 - 1.39.0 | V       |        |\n| 2.0.0+         |         | V      |\n\n  \n\n## Installation\nThe cloudinary_java library is available in [Maven Central](https://mvnrepository.com/artifact/com.cloudinary/cloudinary-core). To use it, add the following dependency to your pom.xml :\n\n```xml\n<dependency>\n    <groupId>com.cloudinary</groupId>\n    <artifactId>cloudinary-http45</artifactId>\n    <version>2.3.1</version>\n</dependency>\n```\n\n## Usage\n### Setup\n\nEach request for building a URL of a remote cloud resource must have the `cloud_name` parameter set.\nEach request to our secure APIs (e.g., image uploads, eager sprite generation) must have the `api_key` and `api_secret` parameters set.\nSee [API, URLs and access identifiers](https://cloudinary.com/documentation/solution_overview#account_and_api_setup) for more details.\n\nSetting the `cloud_name`, `api_key` and `api_secret` parameters can be done either directly in each call to a Cloudinary method,\nby when initializing the Cloudinary object, or by using the CLOUDINARY_URL environment variable / system property.\n\nThe entry point of the library is the Cloudinary object.\n```java\nCloudinary cloudinary = new Cloudinary();\n```\n\nHere's an example of setting the configuration parameters programatically:\n\n```java\nMap config = new HashMap();\nconfig.put(\"cloud_name\", \"n07t21i7\");\nconfig.put(\"api_key\", \"123456789012345\");\nconfig.put(\"api_secret\", \"abcdeghijklmnopqrstuvwxyz12\");\nCloudinary cloudinary = new Cloudinary(config);\n```\n\nAnother example of setting the configuration parameters by providing the CLOUDINARY_URL value to the constructor:\n\n    Cloudinary cloudinary = new Cloudinary(\"cloudinary://123456789012345:abcdeghijklmnopqrstuvwxyz12@n07t21i7\");\n\n### Transform and Optimize Assets\n- [See full documentation](https://cloudinary.com/documentation/java_image_manipulation)\nAny image uploaded to Cloudinary can be transformed and embedded using powerful view helper methods:\n\nThe following example generates the url for accessing an uploaded `sample` image while transforming it to fill a 100x150 rectangle:\n\n```java\ncloudinary.url().transformation(new Transformation().width(100).height(150).crop(\"fill\")).generate(\"sample.jpg\");\n```\n\nAnother example, emedding a smaller version of an uploaded image while generating a 90x90 face detection based thumbnail:\n\n```java\ncloudinary.url().transformation(new Transformation().width(90).height(90).crop(\"thumb\").gravity(\"face\")).generate(\"woman.jpg\");\n```\n\nYou can provide either a Facebook name or a numeric ID of a Facebook profile or a fan page.\n\nEmbedding a Facebook profile to match your graphic design is very simple:\n\n```java\ncloudinary.url().type(\"facebook\").transformation(new Transformation().width(130).height(130).crop(\"fill\").gravity(\"north_west\")).generate(\"billclinton.jpg\");\n```\n\n### File upload\nAssuming you have your Cloudinary configuration parameters defined (`cloud_name`, `api_key`, `api_secret`), uploading to Cloudinary is very simple.\n\nThe following example uploads a local JPG to the cloud:\n\n```java\ncloudinary.uploader().upload(\"my_picture.jpg\", ObjectUtils.emptyMap());\n```\n\nThe uploaded image is assigned a randomly generated public ID. The image is immediately available for download through a CDN:\n\n```java\ncloudinary.url().generate(\"abcfrmo8zul1mafopawefg.jpg\");\n\n# http://res.cloudinary.com/demo/image/upload/abcfrmo8zul1mafopawefg.jpg\n```\n\nYou can also specify your own public ID:\n\n```java\ncloudinary.uploader().upload(\"http://www.example.com/image.jpg\", ObjectUtils.asMap(\"public_id\", \"sample_remote\"));\n\ncloudinary.url().generate(\"sample_remote.jpg\");\n\n# http://res.cloudinary.com/demo/image/upload/sample_remote.jpg\n```\n\n## Contributions\nSee [contributing guidelines](/CONTRIBUTING.md).\n\n## Get Help\n- [Open a Github issue](https://github.com/CloudinaryLtd/cloudinary_java/issues) (for issues related to the SDK)\n- [Open a support ticket](https://cloudinary.com/contact) (for issues related to your account)\n\n## About Cloudinary\nCloudinary is a powerful media API for websites and mobile apps alike, Cloudinary enables developers to efficiently manage, transform, optimize, and deliver images and videos through multiple CDNs. Ultimately, viewers enjoy responsive and personalized visual-media experiences—irrespective of the viewing device.\n\n## Additional Resources\n- [Cloudinary Transformation and REST API References](https://cloudinary.com/documentation/cloudinary_references): Comprehensive references, including syntax and examples for all SDKs.\n- [MediaJams.dev](https://mediajams.dev/): Bite-size use-case tutorials written by and for Cloudinary Developers\n- [DevJams](https://www.youtube.com/playlist?list=PL8dVGjLA2oMr09amgERARsZyrOz_sPvqw): Cloudinary developer podcasts on YouTube.\n- [Cloudinary Academy](https://training.cloudinary.com/): Free self-paced courses, instructor-led virtual courses, and on-site courses.\n- [Code Explorers and Feature Demos](https://cloudinary.com/documentation/code_explorers_demos_index): A one-stop shop for all code explorers, Postman collections, and feature demos found in the docs.\n- [Cloudinary Roadmap](https://cloudinary.com/roadmap): Your chance to follow, vote, or suggest what Cloudinary should develop next.\n- [Cloudinary Facebook Community](https://www.facebook.com/groups/CloudinaryCommunity): Learn from and offer help to other Cloudinary developers.\n- [Cloudinary Account Registration](https://cloudinary.com/users/register/free): Free Cloudinary account registration.\n- [Cloudinary Website](https://cloudinary.com)\n\n## Licence\nReleased under the MIT license.\n"
  },
  {
    "path": "build.gradle",
    "content": "import groovy.json.JsonSlurper\n\nplugins {\n    id 'maven-publish'\n    // Removed old nexus plugin - we'll create bundles manually for Central Portal\n}\n\nallprojects {\n    repositories {\n        mavenCentral()\n    }\n    project.ext.set(\"publishGroupId\", group)\n}\n\n// Removed nexusPublishing block - we'll create bundles manually for Central Portal upload\n\ntasks.create('createTestSubAccount') {\n    doFirst {\n        println(\"Task createTestSubAccount called with module $moduleName\")\n\n        def cloudinaryUrl = \"\"\n\n        // core does not use test clouds, skip (keep empty file for a more readable generic travis test script)\n        if (moduleName != \"core\") {\n            println \"Creating test cloud...\"\n            def baseUrl = new URL('https://sub-account-testing.cloudinary.com/create_sub_account')\n            def connection = baseUrl.openConnection()\n            connection.with {\n                doOutput = true\n                requestMethod = 'POST'\n                def json = new JsonSlurper().parseText(content.text)\n                def cloud = json[\"payload\"][\"cloudName\"]\n                def key = json[\"payload\"][\"cloudApiKey\"]\n                def secret = json[\"payload\"][\"cloudApiSecret\"]\n                cloudinaryUrl = \"CLOUDINARY_URL=cloudinary://$key:$secret@$cloud\"\n            }\n\n        }\n\n        def dir = new File(\"${projectDir.path}${File.separator}tools\")\n        dir.mkdir()\n        def file = new File(dir, \"cloudinary_url.txt\")\n        file.createNewFile()\n        file.text = cloudinaryUrl\n\n        println(\"Test sub-account created succesfully!\")\n    }\n}\n"
  },
  {
    "path": "cloudinary-core/build.gradle",
    "content": "plugins {\n    id 'java-library'\n}\n\ntask ciTest( type: Test )\n\ndependencies {\n    testImplementation \"org.hamcrest:java-hamcrest:2.0.0.0\"\n    testImplementation group: 'pl.pragmatists', name: 'JUnitParams', version: '1.0.5'\n    testImplementation group: 'junit', name: 'junit', version: '4.12'\n}\n\napply from: \"../java_shared.gradle\"\napply from: \"../publish.gradle\"\n\n// Publishing configuration moved to ../publish.gradle"
  },
  {
    "path": "cloudinary-core/src/main/java/com/cloudinary/AccessControlRule.java",
    "content": "package com.cloudinary;\n\nimport com.cloudinary.utils.ObjectUtils;\nimport org.cloudinary.json.JSONObject;\n\nimport java.util.Date;\n\n/**\n * A class representing a single access control rule for a resource. Used as a parameter for {@link Api#update} and {@link Uploader#upload}\n */\npublic class AccessControlRule extends JSONObject {\n\n    /**\n     * Construct a new token access rule\n     * @return The access rule instance\n     */\n    public static AccessControlRule token(){\n        return new AccessControlRule(AccessType.token, null, null);\n    }\n\n    /**\n     * Construct a new anonymous access rule\n     * @param start The start date for the rule\n     * @return The access rule instance\n     */\n    public static AccessControlRule anonymousFrom(Date start){\n        return new AccessControlRule(AccessType.anonymous, start, null);\n    }\n\n    /**\n     * Construct a new anonymous access rule\n     * @param end The end date for the rule\n     * @return The access rule instance\n     */\n    public static AccessControlRule anonymousUntil(Date end){\n        return new AccessControlRule(AccessType.anonymous, null, end);\n    }\n\n    /**\n     * Construct a new anonymous access rule\n     * @param start The start date for the rule\n     * @param end The end date for the rule\n     * @return The access rule instance\n     */\n    public static AccessControlRule anonymous(Date start, Date end){\n        return new AccessControlRule(AccessType.anonymous, start, end);\n    }\n\n    private AccessControlRule(AccessType accessType, Date start, Date end) {\n        put(\"access_type\", accessType.name());\n        if (start != null) {\n            put(\"start\", ObjectUtils.toISO8601(start));\n        }\n\n        if (end != null) {\n            put(\"end\", ObjectUtils.toISO8601(end));\n        }\n    }\n\n    /**\n     * Access type for an access rule\n     */\n    public enum AccessType {\n        anonymous, token\n    }\n}\n"
  },
  {
    "path": "cloudinary-core/src/main/java/com/cloudinary/Api.java",
    "content": "package com.cloudinary;\n\nimport java.util.*;\n\nimport com.cloudinary.api.ApiResponse;\nimport com.cloudinary.api.AuthorizationRequired;\nimport com.cloudinary.api.exceptions.*;\nimport com.cloudinary.metadata.MetadataField;\nimport com.cloudinary.metadata.MetadataDataSource;\nimport com.cloudinary.metadata.MetadataRule;\nimport com.cloudinary.strategies.AbstractApiStrategy;\nimport com.cloudinary.utils.Base64Coder;\nimport com.cloudinary.utils.ObjectUtils;\nimport com.cloudinary.utils.StringUtils;\nimport org.cloudinary.json.JSONArray;\n\n@SuppressWarnings({\"rawtypes\", \"unchecked\"})\npublic class Api {\n\n\n    public AbstractApiStrategy getStrategy() {\n        return strategy;\n    }\n\n    public enum HttpMethod {GET, POST, PUT, DELETE;}\n\n    public final static Map<Integer, Class<? extends Exception>> CLOUDINARY_API_ERROR_CLASSES = new HashMap<Integer, Class<? extends Exception>>();\n\n    static {\n        CLOUDINARY_API_ERROR_CLASSES.put(400, BadRequest.class);\n        CLOUDINARY_API_ERROR_CLASSES.put(401, AuthorizationRequired.class);\n        CLOUDINARY_API_ERROR_CLASSES.put(403, NotAllowed.class);\n        CLOUDINARY_API_ERROR_CLASSES.put(404, NotFound.class);\n        CLOUDINARY_API_ERROR_CLASSES.put(409, AlreadyExists.class);\n        CLOUDINARY_API_ERROR_CLASSES.put(420, RateLimited.class);\n        CLOUDINARY_API_ERROR_CLASSES.put(500, GeneralError.class);\n    }\n\n    public final Cloudinary cloudinary;\n\n    private AbstractApiStrategy strategy;\n\n    protected ApiResponse callApi(HttpMethod method, Iterable<String> uri, Map<String, ? extends Object> params, Map options) throws Exception {\n        if (options == null)\n            options = ObjectUtils.emptyMap();\n\n        String apiKey = ObjectUtils.asString(options.get(\"api_key\"), this.cloudinary.config.apiKey);\n        String apiSecret = ObjectUtils.asString(options.get(\"api_secret\"), this.cloudinary.config.apiSecret);\n        String oauthToken = ObjectUtils.asString(options.get(\"oauth_token\"), this.cloudinary.config.oauthToken);\n\n        validateAuthorization(apiKey, apiSecret, oauthToken);\n\n\n        String authorizationHeader = getAuthorizationHeaderValue(apiKey, apiSecret, oauthToken);\n        String apiUrl = createApiUrl(uri, options);\n        return this.strategy.callApi(method, apiUrl, params, options, authorizationHeader);\n    }\n\n    public Api(Cloudinary cloudinary, AbstractApiStrategy strategy) {\n        this.cloudinary = cloudinary;\n        this.strategy = strategy;\n        this.strategy.init(this);\n    }\n\n    public ApiResponse ping(Map options) throws Exception {\n        if (options == null) options = ObjectUtils.emptyMap();\n        return callApi(HttpMethod.GET, Arrays.asList(\"ping\"), ObjectUtils.emptyMap(), options);\n    }\n\n    public ApiResponse usage(Map options) throws Exception {\n        if (options == null) options = ObjectUtils.emptyMap();\n\n        final List<String> uri = new ArrayList<String>();\n        uri.add(\"usage\");\n\n        Object date = options.get(\"date\");\n\n        if (date != null) {\n            if (date instanceof Date) {\n                date = ObjectUtils.toUsageApiDateFormat((Date) date);\n            }\n\n            uri.add(date.toString());\n        }\n\n        return callApi(HttpMethod.GET, uri, ObjectUtils.emptyMap(), options);\n    }\n\n    public ApiResponse configuration(Map options) throws  Exception {\n        if(options == null) options = ObjectUtils.emptyMap();\n\n        final List<String> uri = new ArrayList<String>();\n        uri.add(\"config\");\n\n        Map params = ObjectUtils.only(options, \"settings\");\n\n        return callApi(HttpMethod.GET, uri, params, options);\n    }\n\n    public ApiResponse resourceTypes(Map options) throws Exception {\n        if (options == null) options = ObjectUtils.emptyMap();\n        return callApi(HttpMethod.GET, Arrays.asList(\"resources\"), ObjectUtils.emptyMap(), options);\n    }\n\n    public ApiResponse resources(Map options) throws Exception {\n        if (options == null) options = ObjectUtils.emptyMap();\n        String resourceType = ObjectUtils.asString(options.get(\"resource_type\"), \"image\");\n        String type = ObjectUtils.asString(options.get(\"type\"));\n        List<String> uri = new ArrayList<String>();\n        uri.add(\"resources\");\n        uri.add(resourceType);\n        if (type != null)\n            uri.add(type);\n        if(options.get(\"fields\") != null) {\n            options.put(\"fields\", StringUtils.join(ObjectUtils.asArray(options.get(\"fields\")), \",\"));\n        }\n        ApiResponse response = callApi(HttpMethod.GET, uri, ObjectUtils.only(options, \"next_cursor\", \"direction\", \"max_results\", \"prefix\", \"tags\", \"context\", \"moderations\", \"start_at\", \"metadata\", \"fields\"), options);\n        return response;\n    }\n\n    public ApiResponse visualSearch(Map options) throws Exception {\n        List<String> uri = new ArrayList<String>();\n        uri.add(\"resources/visual_search\");\n        uri.add(\"image\");\n        if (options.get(\"text\") == null && options.get(\"image_asset_id\") == null && options.get(\"image_url\") == null) {\n            throw new IllegalArgumentException(\"Must supply image file, image url, image asset id or text\");\n        }\n        ApiResponse response = callApi(HttpMethod.GET, uri, options, options);\n        return response;\n    }\n\n    public ApiResponse resourcesByTag(String tag, Map options) throws Exception {\n        if (options == null) options = ObjectUtils.emptyMap();\n        String resourceType = ObjectUtils.asString(options.get(\"resource_type\"), \"image\");\n        if(options.get(\"fields\") != null) {\n            options.put(\"fields\", StringUtils.join(ObjectUtils.asArray(options.get(\"fields\")), \",\"));\n        }\n        ApiResponse response = callApi(HttpMethod.GET, Arrays.asList(\"resources\", resourceType, \"tags\", tag), ObjectUtils.only(options, \"next_cursor\", \"direction\", \"max_results\", \"tags\", \"context\", \"moderations\", \"metadata\", \"fields\"), options);\n        return response;\n    }\n\n    public ApiResponse resourcesByContext(String key, Map options) throws Exception {\n        return resourcesByContext(key, null, options);\n    }\n\n    public ApiResponse resourcesByContext(String key, String value, Map options) throws Exception {\n        if (options == null) options = ObjectUtils.emptyMap();\n        String resourceType = ObjectUtils.asString(options.get(\"resource_type\"), \"image\");\n        if(options.get(\"fields\") != null) {\n            options.put(\"fields\", StringUtils.join(ObjectUtils.asArray(options.get(\"fields\")), \",\"));\n        }\n        Map params = ObjectUtils.only(options, \"next_cursor\", \"direction\", \"max_results\", \"tags\", \"context\", \"moderations\", \"metadata\", \"fields\");\n        params.put(\"key\", key);\n        if (StringUtils.isNotBlank(value)) {\n            params.put(\"value\", value);\n        }\n        return callApi(HttpMethod.GET, Arrays.asList(\"resources\", resourceType, \"context\"), params, options);\n    }\n\n    public ApiResponse resourceByAssetID(String assetId, Map options) throws Exception {\n        if (options == null) options = ObjectUtils.emptyMap();\n        if(options.get(\"fields\") != null) {\n            options.put(\"fields\", StringUtils.join(ObjectUtils.asArray(options.get(\"fields\")), \",\"));\n        }\n        Map params = buildResourceDetailParams(options);\n        ApiResponse response = callApi(HttpMethod.GET, Arrays.asList(\"resources\", assetId), params, options);\n        return response;\n    }\n    public ApiResponse resourcesByAssetIDs(Iterable<String> assetIds, Map options) throws Exception {\n        if (options == null) options = ObjectUtils.emptyMap();\n        Map params = ObjectUtils.only(options, \"public_ids\", \"tags\", \"context\", \"moderations\");\n        params.put(\"asset_ids\", assetIds);\n        ApiResponse response = callApi(HttpMethod.GET, Arrays.asList(\"resources\", \"by_asset_ids\"), params, options);\n        return response;\n    }\n\n    public ApiResponse resourcesByAssetFolder(String assetFolder, Map options) throws Exception {\n        if (options == null) options = ObjectUtils.emptyMap();\n        if(options.get(\"fields\") != null) {\n            options.put(\"fields\", StringUtils.join(ObjectUtils.asArray(options.get(\"fields\")), \",\"));\n        }\n        Map params = ObjectUtils.only(options, \"next_cursor\", \"direction\", \"max_results\", \"tags\", \"context\", \"moderations\", \"fields\");\n        params.put(\"asset_folder\", assetFolder);\n        ApiResponse response = callApi(HttpMethod.GET, Arrays.asList(\"resources/by_asset_folder\"), params, options);\n        return response;\n    }\n\n    public ApiResponse resourcesByIds(Iterable<String> publicIds, Map options) throws Exception {\n        if (options == null) options = ObjectUtils.emptyMap();\n        String resourceType = ObjectUtils.asString(options.get(\"resource_type\"), \"image\");\n        String type = ObjectUtils.asString(options.get(\"type\"), \"upload\");\n        Map params = ObjectUtils.only(options, \"tags\", \"context\", \"moderations\");\n        params.put(\"public_ids\", publicIds);\n        ApiResponse response = callApi(HttpMethod.GET, Arrays.asList(\"resources\", resourceType, type), params, options);\n        return response;\n    }\n\n    public ApiResponse resourcesByModeration(String kind, String status, Map options) throws Exception {\n        if (options == null) options = ObjectUtils.emptyMap();\n        String resourceType = ObjectUtils.asString(options.get(\"resource_type\"), \"image\");\n        if(options.get(\"fields\") != null) {\n            options.put(\"fields\", StringUtils.join(ObjectUtils.asArray(options.get(\"fields\")), \",\"));\n        }\n        ApiResponse response = callApi(HttpMethod.GET, Arrays.asList(\"resources\", resourceType, \"moderations\", kind, status), ObjectUtils.only(options, \"next_cursor\", \"direction\", \"max_results\", \"tags\", \"context\", \"moderations\", \"metadata\", \"fields\"), options);\n        return response;\n    }\n\n    public ApiResponse resource(String public_id, Map options) throws Exception {\n        if (options == null) options = ObjectUtils.emptyMap();\n        String resourceType = ObjectUtils.asString(options.get(\"resource_type\"), \"image\");\n        String type = ObjectUtils.asString(options.get(\"type\"), \"upload\");\n        Map params = buildResourceDetailParams(options);\n\n        ApiResponse response = callApi(HttpMethod.GET, Arrays.asList(\"resources\", resourceType, type, public_id), params, options);\n\n        return response;\n    }\n\n    private Map buildResourceDetailParams(Map options) {\n        return ObjectUtils.only(options, \"exif\", \"colors\", \"faces\", \"coordinates\",\n                        \"image_metadata\", \"pages\", \"phash\", \"max_results\", \"quality_analysis\", \"cinemagraph_analysis\",\n                        \"accessibility_analysis\", \"versions\", \"media_metadata\", \"derived_next_cursor\");\n    }\n\n    public ApiResponse update(String public_id, Map options) throws Exception {\n        if (options == null) options = ObjectUtils.emptyMap();\n        String resourceType = ObjectUtils.asString(options.get(\"resource_type\"), \"image\");\n        String type = ObjectUtils.asString(options.get(\"type\"), \"upload\");\n        Map params = new HashMap<String, Object>();\n        Util.processWriteParameters(options, params);\n        params.put(\"moderation_status\", options.get(\"moderation_status\"));\n        params.put(\"notification_url\", options.get(\"notification_url\"));\n        ApiResponse response = callApi(HttpMethod.POST, Arrays.asList(\"resources\", resourceType, type, public_id),\n                params, options);\n        return response;\n    }\n\n    public ApiResponse deleteResources(Iterable<String> publicIds, Map options) throws Exception {\n        if (options == null) options = ObjectUtils.emptyMap();\n        String resourceType = ObjectUtils.asString(options.get(\"resource_type\"), \"image\");\n        String type = ObjectUtils.asString(options.get(\"type\"), \"upload\");\n        Map params = ObjectUtils.only(options, \"keep_original\", \"invalidate\", \"next_cursor\", \"transformations\");\n        params.put(\"public_ids\", publicIds);\n        return callApi(HttpMethod.DELETE, Arrays.asList(\"resources\", resourceType, type), params, options);\n    }\n\n    public ApiResponse deleteResourcesByAssetIds(Iterable<String> assetIds, Map options) throws Exception {\n        if (options == null) options = ObjectUtils.emptyMap();\n        Map params = ObjectUtils.only(options, \"keep_original\", \"invalidate\", \"next_cursor\", \"transformations\");\n        params.put(\"asset_ids\", assetIds);\n        return callApi(HttpMethod.DELETE, Arrays.asList(\"resources\"), params, options);\n    }\n\n    public ApiResponse deleteDerivedByTransformation(Iterable<String> publicIds, List<Transformation> transformations, Map options) throws Exception {\n        if (options == null) options = ObjectUtils.emptyMap();\n        String resourceType = ObjectUtils.asString(options.get(\"resource_type\"), \"image\");\n        String type = ObjectUtils.asString(options.get(\"type\"), \"upload\");\n        Map params = ObjectUtils.only(options, \"invalidate\", \"next_cursor\");\n        params.put(\"keep_original\", true);\n        params.put(\"public_ids\", publicIds);\n        params.put(\"transformations\", Util.buildEager(transformations));\n        return callApi(HttpMethod.DELETE, Arrays.asList(\"resources\", resourceType, type), params, options);\n    }\n\n    public ApiResponse deleteResourcesByPrefix(String prefix, Map options) throws Exception {\n        if (options == null) options = ObjectUtils.emptyMap();\n        String resourceType = ObjectUtils.asString(options.get(\"resource_type\"), \"image\");\n        String type = ObjectUtils.asString(options.get(\"type\"), \"upload\");\n        Map params = ObjectUtils.only(options, \"keep_original\", \"invalidate\", \"next_cursor\");\n        params.put(\"prefix\", prefix);\n        return callApi(HttpMethod.DELETE, Arrays.asList(\"resources\", resourceType, type), params, options);\n    }\n\n    public ApiResponse deleteResourcesByTag(String tag, Map options) throws Exception {\n        if (options == null) options = ObjectUtils.emptyMap();\n        String resourceType = ObjectUtils.asString(options.get(\"resource_type\"), \"image\");\n        return callApi(HttpMethod.DELETE, Arrays.asList(\"resources\", resourceType, \"tags\", tag), ObjectUtils.only(options, \"keep_original\", \"invalidate\", \"next_cursor\"), options);\n    }\n\n    public ApiResponse deleteAllResources(Map options) throws Exception {\n        if (options == null) options = ObjectUtils.emptyMap();\n        String resourceType = ObjectUtils.asString(options.get(\"resource_type\"), \"image\");\n        String type = ObjectUtils.asString(options.get(\"type\"), \"upload\");\n        Map filtered = ObjectUtils.only(options, \"keep_original\", \"invalidate\", \"next_cursor\");\n        filtered.put(\"all\", true);\n        return callApi(HttpMethod.DELETE, Arrays.asList(\"resources\", resourceType, type), filtered, options);\n    }\n\n    public ApiResponse deleteDerivedResources(Iterable<String> derivedResourceIds, Map options) throws Exception {\n        if (options == null) options = ObjectUtils.emptyMap();\n        return callApi(HttpMethod.DELETE, Arrays.asList(\"derived_resources\"), ObjectUtils.asMap(\"derived_resource_ids\", derivedResourceIds), options);\n    }\n\n    public ApiResponse tags(Map options) throws Exception {\n        if (options == null) options = ObjectUtils.emptyMap();\n        String resourceType = ObjectUtils.asString(options.get(\"resource_type\"), \"image\");\n        return callApi(HttpMethod.GET, Arrays.asList(\"tags\", resourceType), ObjectUtils.only(options, \"next_cursor\", \"max_results\", \"prefix\"), options);\n    }\n\n    public ApiResponse transformations(Map options) throws Exception {\n        if (options == null) options = ObjectUtils.emptyMap();\n        return callApi(HttpMethod.GET, Arrays.asList(\"transformations\"), ObjectUtils.only(options, \"next_cursor\", \"max_results\", \"named\"), options);\n    }\n\n    public ApiResponse transformation(String transformation, Map options) throws Exception {\n        if (options == null) options = ObjectUtils.emptyMap();\n        Map map = ObjectUtils.only(options, \"next_cursor\", \"max_results\");\n        map.put(\"transformation\", transformation);\n        return callApi(HttpMethod.GET, Arrays.asList(\"transformations\"), map, options);\n    }\n\n    public ApiResponse deleteTransformation(String transformation, Map options) throws Exception {\n        if (options == null) options = ObjectUtils.emptyMap();\n        Map updates = ObjectUtils.asMap(\"transformation\", transformation);\n        return callApi(HttpMethod.DELETE, Arrays.asList(\"transformations\"), updates, options);\n    }\n\n    // updates - currently only supported update are:\n    // \"allowed_for_strict\": boolean flag\n    // \"unsafe_update\": transformation string\n    public ApiResponse updateTransformation(String transformation, Map updates, Map options) throws Exception {\n        if (options == null) options = ObjectUtils.emptyMap();\n        updates.put(\"transformation\", transformation);\n        return callApi(HttpMethod.PUT, Arrays.asList(\"transformations\"), updates, options);\n    }\n\n    public ApiResponse createTransformation(String name, String definition, Map options) throws Exception {\n        return callApi(HttpMethod.POST, \n                Arrays.asList(\"transformations\"), \n                ObjectUtils.asMap(\"transformation\", definition, \"name\", name), options);\n    }\n\n    public ApiResponse uploadPresets(Map options) throws Exception {\n        if (options == null) options = ObjectUtils.emptyMap();\n        return callApi(HttpMethod.GET, Arrays.asList(\"upload_presets\"), ObjectUtils.only(options, \"next_cursor\", \"max_results\"), options);\n    }\n\n    public ApiResponse uploadPreset(String name, Map options) throws Exception {\n        if (options == null) options = ObjectUtils.emptyMap();\n        return callApi(HttpMethod.GET, Arrays.asList(\"upload_presets\", name), ObjectUtils.only(options, \"max_results\"), options);\n    }\n\n    public ApiResponse deleteUploadPreset(String name, Map options) throws Exception {\n        if (options == null) options = ObjectUtils.emptyMap();\n        return callApi(HttpMethod.DELETE, Arrays.asList(\"upload_presets\", name), ObjectUtils.emptyMap(), options);\n    }\n\n    public ApiResponse updateUploadPreset(String name, Map options) throws Exception {\n        if (options == null) options = ObjectUtils.emptyMap();\n        Map params = Util.buildUploadParams(options);\n        Util.clearEmpty(params);\n        params.putAll(ObjectUtils.only(options, \"unsigned\", \"disallow_public_id\"));\n        return callApi(HttpMethod.PUT, Arrays.asList(\"upload_presets\", name), params, options);\n    }\n\n    public ApiResponse createUploadPreset(Map options) throws Exception {\n        if (options == null) options = ObjectUtils.emptyMap();\n        Map params = Util.buildUploadParams(options);\n        Util.clearEmpty(params);\n        params.putAll(ObjectUtils.only(options, \"name\", \"unsigned\", \"disallow_public_id\"));\n        return callApi(HttpMethod.POST, Arrays.asList(\"upload_presets\"), params, options);\n    }\n\n    public ApiResponse rootFolders(Map options) throws Exception {\n        if (options == null)\n            options = ObjectUtils.emptyMap();\n        return callApi(HttpMethod.GET, Arrays.asList(\"folders\"),\n                extractParams(options, Arrays.asList(\"max_results\", \"next_cursor\")),\n                options);\n    }\n\n    public ApiResponse subFolders(String ofFolderPath, Map options) throws Exception {\n        if (options == null)\n            options = ObjectUtils.emptyMap();\n        return callApi(HttpMethod.GET, Arrays.asList(\"folders\", ofFolderPath),\n                extractParams(options, Arrays.asList(\"max_results\", \"next_cursor\")),\n                options);\n    }\n\n    //Creates an empty folder\n    public ApiResponse createFolder(String folderName, Map options) throws Exception {\n        if (options == null)\n            options = ObjectUtils.emptyMap();\n        return callApi(HttpMethod.POST, Arrays.asList(\"folders\", folderName), ObjectUtils.emptyMap(), options);\n    }\n\n    public ApiResponse restore(Iterable<String> publicIds, Map options) throws Exception {\n        if (options == null)\n            options = ObjectUtils.emptyMap();\n        String resourceType = ObjectUtils.asString(options.get(\"resource_type\"), \"image\");\n        String type = ObjectUtils.asString(options.get(\"type\"), \"upload\");\n        Map params = new HashMap<String, Object>();\n        params.put(\"public_ids\", publicIds);\n        params.put(\"versions\", options.get(\"versions\"));\n\n        ApiResponse response = callApi(HttpMethod.POST, Arrays.asList(\"resources\", resourceType, type, \"restore\"), params, options);\n        return response;\n    }\n\n    public ApiResponse restoreByAssetIds(Iterable<String> assetIds, Map options) throws Exception {\n        if (options == null)\n            options = ObjectUtils.emptyMap();\n        Map params = new HashMap<String, Object>();\n        params.put(\"asset_ids\", assetIds);\n        return callApi(HttpMethod.POST, Arrays.asList(\"resources\", \"restore\"), params, options);\n    }\n\n    public ApiResponse uploadMappings(Map options) throws Exception {\n        if (options == null)\n            options = ObjectUtils.emptyMap();\n        return callApi(HttpMethod.GET, Arrays.asList(\"upload_mappings\"),\n                ObjectUtils.only(options, \"next_cursor\", \"max_results\"), options);\n    }\n\n    public ApiResponse uploadMapping(String name, Map options) throws Exception {\n        if (options == null)\n            options = ObjectUtils.emptyMap();\n        return callApi(HttpMethod.GET, Arrays.asList(\"upload_mappings\"), ObjectUtils.asMap(\"folder\", name), options);\n    }\n\n    public ApiResponse deleteUploadMapping(String name, Map options) throws Exception {\n        if (options == null)\n            options = ObjectUtils.emptyMap();\n        return callApi(HttpMethod.DELETE, Arrays.asList(\"upload_mappings\"), ObjectUtils.asMap(\"folder\", name), options);\n    }\n\n    public ApiResponse updateUploadMapping(String name, Map options) throws Exception {\n        if (options == null)\n            options = ObjectUtils.emptyMap();\n        Map params = new HashMap<String, Object>();\n        params.put(\"folder\", name);\n        params.putAll(ObjectUtils.only(options, \"template\"));\n        return callApi(HttpMethod.PUT, Arrays.asList(\"upload_mappings\"), params, options);\n    }\n\n    public ApiResponse createUploadMapping(String name, Map options) throws Exception {\n        if (options == null)\n            options = ObjectUtils.emptyMap();\n        Map params = new HashMap<String, Object>();\n        params.put(\"folder\", name);\n        params.putAll(ObjectUtils.only(options, \"template\"));\n        return callApi(HttpMethod.POST, Arrays.asList(\"upload_mappings\"), params, options);\n    }\n\n    public ApiResponse publishByPrefix(String prefix, Map options) throws Exception {\n        return publishResource(\"prefix\", prefix, options);\n    }\n\n    public ApiResponse publishByTag(String tag, Map options) throws Exception {\n        return publishResource(\"tag\", tag, options);\n    }\n\n    public ApiResponse publishByIds(Iterable<String> publicIds, Map options) throws Exception {\n        return publishResource(\"public_ids\", publicIds, options);\n    }\n\n    private ApiResponse publishResource(String byKey, Object value, Map options) throws Exception {\n        if (options == null) options = ObjectUtils.emptyMap();\n        String resourceType = ObjectUtils.asString(options.get(\"resource_type\"), \"image\");\n        List<String> uri = new ArrayList<String>();\n        uri.add(\"resources\");\n        uri.add(resourceType);\n        uri.add(\"publish_resources\");\n        Map params = new HashMap<String, Object>();\n        params.put(byKey, value);\n        params.putAll(ObjectUtils.only(options, \"invalidate\", \"overwrite\", \"type\"));\n        return callApi(HttpMethod.POST, uri, params, options);\n    }\n\n    /**\n     * Create a new streaming profile\n     *\n     * @param name            the of the profile\n     * @param displayName     the display name of the profile\n     * @param representations a collection of Maps with a transformation key\n     * @param options         additional options\n     * @return the new streaming profile\n     * @throws Exception an exception\n     */\n    public ApiResponse createStreamingProfile(String name, String displayName, List<Map> representations, Map options) throws Exception {\n        if (options == null)\n            options = ObjectUtils.emptyMap();\n        List<Map> serializedRepresentations = new ArrayList<Map>(representations.size());\n        for (Map t : representations) {\n            final Object transformation = t.get(\"transformation\");\n            serializedRepresentations.add(ObjectUtils.asMap(\"transformation\", transformation.toString()));\n        }\n        List<String> uri = Collections.singletonList(\"streaming_profiles\");\n        final Map params = ObjectUtils.asMap(\n                \"name\", name,\n                \"representations\", new JSONArray(serializedRepresentations.toArray())\n        );\n        if (displayName != null) {\n            params.put(\"display_name\", displayName);\n        }\n        return callApi(HttpMethod.POST, uri, params, options);\n    }\n\n    /**\n     * @see Api#createStreamingProfile(String, String, List, Map)\n     */\n    public ApiResponse createStreamingProfile(String name, String displayName, List<Map> representations) throws Exception {\n        return createStreamingProfile(name, displayName, representations, null);\n    }\n\n    /**\n     * Get a streaming profile information\n     *\n     * @param name    the name of the profile to fetch\n     * @param options additional options\n     * @return a streaming profile\n     * @throws Exception an exception\n     */\n    public ApiResponse getStreamingProfile(String name, Map options) throws Exception {\n        if (options == null)\n            options = ObjectUtils.emptyMap();\n        List<String> uri = Arrays.asList(\"streaming_profiles\", name);\n\n        return callApi(HttpMethod.GET, uri, ObjectUtils.emptyMap(), options);\n\n    }\n\n    /**\n     * @see Api#getStreamingProfile(String, Map)\n     */\n    public ApiResponse getStreamingProfile(String name) throws Exception {\n        return getStreamingProfile(name, null);\n    }\n\n    /**\n     * List Streaming profiles\n     *\n     * @param options additional options\n     * @return a list of all streaming profiles defined for the current cloud\n     * @throws Exception an exception\n     */\n    public ApiResponse listStreamingProfiles(Map options) throws Exception {\n        if (options == null)\n            options = ObjectUtils.emptyMap();\n        List<String> uri = Collections.singletonList(\"streaming_profiles\");\n        return callApi(HttpMethod.GET, uri, ObjectUtils.emptyMap(), options);\n\n    }\n\n    /**\n     * @see Api#listStreamingProfiles(Map)\n     */\n    public ApiResponse listStreamingProfiles() throws Exception {\n        return listStreamingProfiles(null);\n    }\n\n    /**\n     * Delete a streaming profile information. Predefined profiles are restored to the default setting.\n     *\n     * @param name    the name of the profile to delete\n     * @param options additional options\n     * @return a streaming profile\n     * @throws Exception an exception\n     */\n    public ApiResponse deleteStreamingProfile(String name, Map options) throws Exception {\n        if (options == null)\n            options = ObjectUtils.emptyMap();\n        List<String> uri = Arrays.asList(\"streaming_profiles\", name);\n\n        return callApi(HttpMethod.DELETE, uri, ObjectUtils.emptyMap(), options);\n\n    }\n\n    /**\n     * @see Api#deleteStreamingProfile(String, Map)\n     */\n    public ApiResponse deleteStreamingProfile(String name) throws Exception {\n        return deleteStreamingProfile(name, null);\n    }\n\n    /**\n     * Create a new streaming profile\n     *\n     * @param name            the of the profile\n     * @param displayName     the display name of the profile\n     * @param representations a collection of Maps with a transformation key\n     * @param options         additional options\n     * @return the new streaming profile\n     * @throws Exception an exception\n     */\n    public ApiResponse updateStreamingProfile(String name, String displayName, List<Map> representations, Map options) throws Exception {\n        if (options == null)\n            options = ObjectUtils.emptyMap();\n        List<Map> serializedRepresentations;\n        final Map params = new HashMap();\n        List<String> uri = Arrays.asList(\"streaming_profiles\", name);\n\n        if (representations != null) {\n            serializedRepresentations = new ArrayList<Map>(representations.size());\n            for (Map t : representations) {\n                final Object transformation = t.get(\"transformation\");\n                serializedRepresentations.add(ObjectUtils.asMap(\"transformation\", transformation.toString()));\n            }\n            params.put(\"representations\", new JSONArray(serializedRepresentations.toArray()));\n        }\n        if (displayName != null) {\n            params.put(\"display_name\", displayName);\n        }\n        return callApi(HttpMethod.PUT, uri, params, options);\n    }\n\n    /**\n     * @see Api#updateStreamingProfile(String, String, List, Map)\n     */\n    public ApiResponse updateStreamingProfile(String name, String displayName, List<Map> representations) throws Exception {\n        return createStreamingProfile(name, displayName, representations);\n    }\n\n    /**\n     * Update access mode of one or more resources by prefix\n     *\n     * @param accessMode The new access mode, \"public\" or  \"authenticated\"\n     * @param prefix     The prefix by which to filter applicable resources\n     * @param options    additional options\n     *                   <ul>\n     *                   <li>resource_type - (default \"image\") - the type of resources to modify</li>\n     *                   <li>max_results - optional - the maximum resources to process in a single invocation</li>\n     *                   <li>next_cursor - optional - provided by a previous call to the method</li>\n     *                   </ul>\n     * @return a map of the returned values\n     * <ul>\n     * <li>updated - an array of resources</li>\n     * <li>next_cursor - optional - provided if more resources can be processed</li>\n     * </ul>\n     * @throws ApiException an API exception\n     */\n    public ApiResponse updateResourcesAccessModeByPrefix(String accessMode, String prefix, Map options) throws Exception {\n        return updateResourcesAccessMode(accessMode, \"prefix\", prefix, options);\n    }\n\n    /**\n     * Update access mode of one or more resources by tag\n     *\n     * @param accessMode The new access mode, \"public\" or  \"authenticated\"\n     * @param tag        The tag by which to filter applicable resources\n     * @param options    additional options\n     *                   <ul>\n     *                   <li>resource_type - (default \"image\") - the type of resources to modify</li>\n     *                   <li>max_results - optional - the maximum resources to process in a single invocation</li>\n     *                   <li>next_cursor - optional - provided by a previous call to the method</li>\n     *                   </ul>\n     * @return a map of the returned values\n     * <ul>\n     * <li>updated - an array of resources</li>\n     * <li>next_cursor - optional - provided if more resources can be processed</li>\n     * </ul>\n     * @throws ApiException an API exception\n     */\n    public ApiResponse updateResourcesAccessModeByTag(String accessMode, String tag, Map options) throws Exception {\n        return updateResourcesAccessMode(accessMode, \"tag\", tag, options);\n    }\n\n    /**\n     * Delete a folder (must be empty).\n     *\n     * @param folder  The full path of the folder to delete\n     * @param options additional options.\n     * @return The operation result.\n     * @throws Exception When the folder isn't empty or doesn't exist.\n     */\n    public ApiResponse deleteFolder(String folder, Map options) throws Exception {\n        if (options == null || options.isEmpty()) options = ObjectUtils.asMap();\n        List<String> uri = Arrays.asList(\"folders\", folder);\n        Map params = ObjectUtils.only(options, \"skip_backup\");\n        return callApi(HttpMethod.DELETE, uri, params, options);\n    }\n\n    /**\n     * Update access mode of one or more resources by publicIds\n     *\n     * @param accessMode The new access mode, \"public\" or  \"authenticated\"\n     * @param publicIds  A list of public ids of resources to be updated\n     * @param options    additional options\n     *                   <ul>\n     *                   <li>resource_type - (default \"image\") - the type of resources to modify</li>\n     *                   <li>max_results - optional - the maximum resources to process in a single invocation</li>\n     *                   <li>next_cursor - optional - provided by a previous call to the method</li>\n     *                   </ul>\n     * @return a map of the returned values\n     * <ul>\n     * <li>updated - an array of resources</li>\n     * <li>next_cursor - optional - provided if more resources can be processed</li>\n     * </ul>\n     * @throws ApiException an API exception\n     */\n    public ApiResponse updateResourcesAccessModeByIds(String accessMode, Iterable<String> publicIds, Map options) throws Exception {\n        return updateResourcesAccessMode(accessMode, \"public_ids\", publicIds, options);\n    }\n\n    private ApiResponse updateResourcesAccessMode(String accessMode, String byKey, Object value, Map options) throws Exception {\n        if (options == null) options = ObjectUtils.emptyMap();\n        String resourceType = ObjectUtils.asString(options.get(\"resource_type\"), \"image\");\n        String type = ObjectUtils.asString(options.get(\"type\"), \"upload\");\n        List<String> uri = Arrays.asList(\"resources\", resourceType, type, \"update_access_mode\");\n        Map params = ObjectUtils.only(options, \"next_cursor\", \"max_results\");\n        params.put(\"access_mode\", accessMode);\n        params.put(byKey, value);\n        return callApi(HttpMethod.POST, uri, params, options);\n    }\n\n    /**\n     * Add a new metadata field definition\n     *\n     * @param field The field to add.\n     * @return A map representing the newly added field.\n     * @throws Exception\n     */\n    public ApiResponse addMetadataField(MetadataField field) throws Exception {\n        return callApi(HttpMethod.POST, Collections.singletonList(\"metadata_fields\"),\n                ObjectUtils.toMap(field), ObjectUtils.asMap(\"content_type\", \"json\"));\n    }\n\n    /**\n     * List all the metadata field definitions (structure, not values)\n     *\n     * @return A map containing the list of field definitions maps.\n     * @throws Exception\n     */\n    public ApiResponse listMetadataFields() throws Exception {\n        return callApi(HttpMethod.GET, Collections.singletonList(\"metadata_fields\"), Collections.<String, Object>emptyMap(), Collections.emptyMap());\n    }\n\n    /**\n     * Get a metadata field definition by id\n     *\n     * @param fieldExternalId The id of the field to retrieve\n     * @return The fields definitions.\n     * @throws Exception\n     */\n    public ApiResponse metadataFieldByFieldId(String fieldExternalId) throws Exception {\n        return callApi(HttpMethod.GET, Arrays.asList(\"metadata_fields\", fieldExternalId), Collections.<String, Object>emptyMap(), Collections.emptyMap());\n    }\n\n    /**\n     * Update the definitions of a single metadata field.\n     *\n     * @param fieldExternalId The id of the field to update\n     * @param field           The field definition\n     * @return The updated fields definition.\n     * @throws Exception\n     */\n    public ApiResponse updateMetadataField(String fieldExternalId, MetadataField field) throws Exception {\n        List<String> uri = Arrays.asList(\"metadata_fields\", fieldExternalId);\n        return callApi(HttpMethod.PUT, uri, ObjectUtils.toMap(field), Collections.singletonMap(\"content_type\", \"json\"));\n    }\n\n    /**\n     * Update the datasource entries for a given field\n     *\n     * @param fieldExternalId The id of the field to update\n     * @param entries         A list of datasource entries. Existing entries (according to entry id) will be updated,\n     *                        new entries will be added.\n     * @return The updated field definition.\n     * @throws Exception\n     */\n    public ApiResponse updateMetadataFieldDatasource(String fieldExternalId, List<MetadataDataSource.Entry> entries) throws Exception {\n        List<String> uri = Arrays.asList(\"metadata_fields\", fieldExternalId, \"datasource\");\n        return callApi(HttpMethod.PUT, uri, Collections.singletonMap(\"values\", entries), Collections.singletonMap(\"content_type\", \"json\"));\n    }\n\n    /**\n     * Delete data source entries for a given field\n     *\n     * @param fieldExternalId   The id of the field to update\n     * @param entriesExternalId The ids of all the entries to delete from the data source\n     * @return The remaining datasource entries.\n     * @throws Exception\n     */\n    public ApiResponse deleteDatasourceEntries(String fieldExternalId, List<String> entriesExternalId) throws Exception {\n        List<String> uri = Arrays.asList(\"metadata_fields\", fieldExternalId, \"datasource\");\n        return callApi(HttpMethod.DELETE, uri, Collections.singletonMap(\"external_ids\", entriesExternalId), Collections.emptyMap());\n    }\n\n    /**\n     * Restore deleted data source entries for a given field\n     *\n     * @param fieldExternalId   The id of the field to operate\n     * @param entriesExternalId The ids of all the entries to restore from the data source\n     * @return The datasource entries state after restore\n     * @throws Exception\n     */\n    public ApiResponse restoreDatasourceEntries(String fieldExternalId, List<String> entriesExternalId) throws Exception {\n        List<String> uri = Arrays.asList(\"metadata_fields\", fieldExternalId, \"datasource_restore\");\n        return callApi(HttpMethod.POST, uri, Collections.singletonMap(\"external_ids\", entriesExternalId), Collections.singletonMap(\"content_type\", \"json\"));\n    }\n\n    /**\n     * Delete a field definition.\n     *\n     * @param fieldExternalId The id of the field to delete\n     * @return A map with a \"message\" key. \"ok\" value indicates a successful deletion.\n     * @throws Exception\n     */\n    public ApiResponse deleteMetadataField(String fieldExternalId) throws Exception {\n        List<String> uri = Arrays.asList(\"metadata_fields\", fieldExternalId);\n        return callApi(HttpMethod.DELETE, uri, Collections.<String, Object>emptyMap(), Collections.emptyMap());\n    }\n\n    /**\n     * Reorders metadata fields.\n     *\n     * @param orderBy Criteria for the order (one of the fields 'label', 'external_id', 'created_at')\n     * @param direction Optional (gets either asc or desc)\n     * @param options Additional options\n     * @return List of metadata fields in their new order\n     * @throws Exception\n     */\n    public ApiResponse reorderMetadataFields(String orderBy, String direction, Map options) throws Exception {\n        if (orderBy == null) {\n            throw new IllegalArgumentException(\"Must supply orderBy\");\n        }\n\n        List<String> uri = Arrays.asList(\"metadata_fields\", \"order\");\n        Map<String, Object> map = ObjectUtils.asMap(\"order_by\", orderBy);\n        if (direction != null) {\n            map.put(\"direction\", direction);\n        }\n\n        return callApi(HttpMethod.PUT, uri, map, options);\n    }\n\n    public ApiResponse listMetadataRules(Map options) throws Exception {\n        if (options == null || options.isEmpty()) options = ObjectUtils.asMap();\n        final Map params = new HashMap();\n        List<String> uri = Arrays.asList(\"metadata_rules\");\n        return callApi(HttpMethod.GET, uri, params, options);\n    }\n\n    public ApiResponse addMetadataRule(MetadataRule rule, Map options) throws Exception {\n        if (options == null || options.isEmpty()) options = ObjectUtils.asMap();\n        options.put(\"content_type\", \"json\");\n        final Map params = rule.asMap();\n        List<String> uri = Arrays.asList(\"metadata_rules\");\n        return callApi(HttpMethod.POST, uri, params, options);\n    }\n\n    public ApiResponse updateMetadataRule(String externalId, MetadataRule rule, Map options) throws Exception {\n        if (options == null || options.isEmpty()) options = ObjectUtils.asMap();\n        options.put(\"content_type\", \"json\");\n        final Map params = rule.asMap();\n        List<String> uri = Arrays.asList(\"metadata_rules\", externalId);\n        return callApi(HttpMethod.PUT, uri, params, options);\n    }\n\n    public ApiResponse deleteMetadataRule(String externalId, Map options) throws Exception {\n        if (options == null || options.isEmpty()) options = ObjectUtils.asMap();\n        List<String> uri = Arrays.asList(\"metadata_rules\", externalId);\n        return callApi(HttpMethod.DELETE, uri, ObjectUtils.emptyMap(), options);\n    }\n\n    public ApiResponse analyze(String inputType, String analysisType, String uri, Map options) throws Exception {\n        if (options == null || options.isEmpty()) options = ObjectUtils.asMap();\n        List<String> url = Arrays.asList(\"analysis\", \"analyze\", inputType);\n        options.put(\"api_version\", \"v2\");\n        options.put(\"content_type\", \"json\");\n        final Map params = new HashMap();\n        params.put(\"analysis_type\", analysisType);\n        params.put(\"uri\", uri);\n        return callApi(HttpMethod.POST, url, params, options);\n    }\n\n    public ApiResponse renameFolder(String path, String toPath, Map options) throws Exception {\n        if (options == null || options.isEmpty()) options = ObjectUtils.asMap();\n        List<String> url = Arrays.asList(\"folders\", path);\n\n        final Map params = new HashMap();\n        params.put(\"to_folder\", toPath);\n\n        return callApi(HttpMethod.PUT, url, params, options);\n\n    }\n\n    public ApiResponse deleteBackedUpAssets(String assetId, String[] versionIds, Map options) throws Exception {\n        if (options == null || options.isEmpty()) options = ObjectUtils.asMap();\n        if (StringUtils.isEmpty(assetId)) {\n            throw new IllegalArgumentException(\"AssetId parameter is required\");\n        }\n\n        if (versionIds == null || versionIds.length == 0) {\n            throw new IllegalArgumentException(\"VersionIds parameter is required\");\n        }\n\n        List<String> url = Arrays.asList(\"resources\", \"backup\", assetId);\n\n        Map<String, Object> params = new HashMap<String, Object>();\n        params.put(\"version_ids[]\", StringUtils.join(versionIds, \"&\"));\n\n        return callApi(HttpMethod.DELETE, url, params, options);\n\n    }\n\n    private Map<String, ?> extractParams(Map options, List<String> keys) {\n        Map<String, Object> result = new HashMap<String, Object>();\n        for (String key : keys) {\n            Object option = options.get(key);\n\n            if (option != null) {\n                result.put(key, option);\n            }\n        }\n        return result;\n    }\n\n    protected void validateAuthorization(String apiKey, String apiSecret, String oauthToken) {\n        if (oauthToken == null) {\n            if (apiKey == null) throw new IllegalArgumentException(\"Must supply api_key\");\n            if (apiSecret == null) throw new IllegalArgumentException(\"Must supply api_secret\");\n        }\n    }\n\n    protected String getAuthorizationHeaderValue(String apiKey, String apiSecret, String oauthToken) {\n        if (oauthToken != null){\n            return \"Bearer \" + oauthToken;\n        } else {\n            return \"Basic \" + Base64Coder.encodeString(apiKey + \":\" + apiSecret);\n        }\n    }\n\n    protected String createApiUrl (Iterable<String> uri, Map options){\n        String version = ObjectUtils.asString(options.get(\"api_version\"), \"v1_1\");\n        String prefix = ObjectUtils.asString(options.get(\"upload_prefix\"), ObjectUtils.asString(this.cloudinary.config.uploadPrefix, \"https://api.cloudinary.com\"));\n        String cloudName = ObjectUtils.asString(options.get(\"cloud_name\"), this.cloudinary.config.cloudName);\n        if (cloudName == null) throw new IllegalArgumentException(\"Must supply cloud_name\");\n        String apiUrl = StringUtils.join(Arrays.asList(prefix, version, cloudName), \"/\");\n        for (String component : uri) {\n            component = SmartUrlEncoder.encode(component);\n            apiUrl = apiUrl + \"/\" + component;\n\n        }\n        return apiUrl;\n    }\n}\n"
  },
  {
    "path": "cloudinary-core/src/main/java/com/cloudinary/ArchiveParams.java",
    "content": "package com.cloudinary;\n\nimport java.util.Arrays;\nimport java.util.HashMap;\nimport java.util.Map;\n\npublic class ArchiveParams {\n    public static final String FORMAT_ZIP = \"zip\";\n\n    public static final String MODE_DOWNLOAD = \"download\";\n    public static final String MODE_CREATE = \"create\";\n\n    private String resourceType = \"image\";\n    private String type = null;\n    private String mode = MODE_CREATE;\n    private String targetFormat = null;\n    private String targetPublicId = null;\n    private boolean flattenFolders = false;\n    private boolean flattenTransformations = false;\n    private boolean useOriginalFilename = false;\n    private boolean async = false;\n    private boolean keepDerived = false;\n    private boolean skipTransformationName = false;\n    private boolean allowMissing = false;\n    private String notificationUrl = null;\n    private String[] targetTags = null;\n    private String[] tags = null;\n    private String[] publicIds = null;\n    private String[] fullyQualifiedPublicIds = null;\n    private String[] prefixes = null;\n    private Transformation[] transformations = null;\n    private Long expiresAt = null;\n\n    public String resourceType() {\n        return resourceType;\n    }\n\n    public ArchiveParams resourceType(String resourceType) {\n        if (resourceType == null)\n            throw new IllegalArgumentException(\"resource type must be non-null\");\n        this.resourceType = resourceType;\n        return this;\n    }\n\n    public String type() {\n        return type;\n    }\n\n    public ArchiveParams type(String type) {\n        this.type = type;\n        return this;\n    }\n\n    public String mode() {\n        return mode;\n    }\n\n    public ArchiveParams mode(String mode) {\n        this.mode = mode;\n        return this;\n    }\n\n    public String targetFormat() {\n        return targetFormat;\n    }\n\n    public ArchiveParams targetFormat(String targetFormat) {\n        this.targetFormat = targetFormat;\n        return this;\n    }\n\n    public String targetPublicId() {\n        return targetPublicId;\n    }\n\n    public ArchiveParams targetPublicId(String targetPublicId) {\n        this.targetPublicId = targetPublicId;\n        return this;\n    }\n\n    public boolean isFlattenFolders() {\n        return flattenFolders;\n    }\n\n    public ArchiveParams flattenFolders(boolean flattenFolders) {\n        this.flattenFolders = flattenFolders;\n        return this;\n    }\n\n    public boolean isFlattenTransformations() {\n        return flattenTransformations;\n    }\n\n    public ArchiveParams flattenTransformations(boolean flattenTransformations) {\n        this.flattenTransformations = flattenTransformations;\n        return this;\n    }\n\n    public boolean isUseOriginalFilename() {\n        return useOriginalFilename;\n    }\n\n    public ArchiveParams useOriginalFilename(boolean useOriginalFilename) {\n        this.useOriginalFilename = useOriginalFilename;\n        return this;\n    }\n\n    public boolean isAsync() {\n        return async;\n    }\n\n    public ArchiveParams async(boolean async) {\n        this.async = async;\n        return this;\n    }\n\n    public boolean isSkipTransformationName() {\n        return skipTransformationName;\n    }\n\n    public ArchiveParams skipTransformationName(boolean skipTransformationName) {\n        this.skipTransformationName = skipTransformationName;\n        return this;\n    }\n\n    public boolean isAllowMissing(){\n        return allowMissing;\n    }\n\n    public ArchiveParams allowMissing(boolean allowMissing){\n        this.allowMissing = allowMissing;\n        return this;\n    }\n\n    public boolean isKeepDerived() {\n        return keepDerived;\n    }\n\n    public ArchiveParams keepDerived(boolean keepDerived) {\n        this.keepDerived = keepDerived;\n        return this;\n    }\n\n    public String notificationUrl() {\n        return notificationUrl;\n    }\n\n    public ArchiveParams notificationUrl(String notificationUrl) {\n        this.notificationUrl = notificationUrl;\n        return this;\n    }\n\n    public String[] targetTags() {\n        return targetTags;\n    }\n\n    public ArchiveParams targetTags(String[] targetTags) {\n        this.targetTags = targetTags;\n        return this;\n    }\n\n    public String[] tags() {\n        return tags;\n    }\n\n    public ArchiveParams tags(String[] tags) {\n        this.tags = tags;\n        return this;\n    }\n\n    public String[] publicIds() {\n        return publicIds;\n    }\n\n    public ArchiveParams publicIds(String[] publicIds) {\n        this.publicIds = publicIds;\n        return this;\n    }\n\n    public String[] fully_qualified_public_ids() {\n        return fullyQualifiedPublicIds;\n    }\n\n    public ArchiveParams fullyQualifiedPublicIds(String[] fullyQualifiedPublicIds) {\n        this.fullyQualifiedPublicIds = fullyQualifiedPublicIds;\n        return this;\n    }\n\n    public String[] prefixes() {\n        return prefixes;\n    }\n\n    public ArchiveParams prefixes(String[] prefixes) {\n        this.prefixes = prefixes;\n        return this;\n    }\n\n    public Transformation[] transformations() {\n        return transformations;\n    }\n\n    public ArchiveParams transformations(Transformation[] transformations) {\n        this.transformations = transformations;\n        return this;\n    }\n\n    public ArchiveParams expiresAt(Long expiresAt) {\n        this.expiresAt = expiresAt;\n        return this;\n    }\n\n    public Long expiresAt(){\n        return expiresAt;\n    }\n\n    public Map<String, Object> toMap() {\n        Map<String, Object> params = new HashMap<String, Object>();\n        params.put(\"resource_type\", resourceType);\n        params.put(\"type\", type);\n        params.put(\"mode\", mode);\n        if (targetPublicId != null)\n            params.put(\"target_public_id\", targetPublicId);\n        params.put(\"flatten_folders\", flattenFolders);\n        params.put(\"flatten_transformations\", flattenTransformations);\n        params.put(\"use_original_filename\", useOriginalFilename);\n        params.put(\"async\", async);\n        params.put(\"keep_derived\", keepDerived);\n        params.put(\"skip_transformation_name\", skipTransformationName);\n        params.put(\"allow_missing\", allowMissing);\n        if (notificationUrl != null)\n            params.put(\"notification_url\", notificationUrl);\n        if (targetTags != null)\n            params.put(\"target_tags\", targetTags);\n        if (tags != null)\n            params.put(\"tags\", tags);\n        if (publicIds != null)\n            params.put(\"public_ids\", publicIds);\n        if(fullyQualifiedPublicIds !=null){\n            params.put(\"fully_qualified_public_ids\", fullyQualifiedPublicIds);\n        }\n        if (prefixes != null)\n            params.put(\"prefixes\", prefixes);\n        if (transformations != null) {\n            params.put(\"transformations\", Arrays.asList(transformations));\n        }\n        if (expiresAt != null){\n            params.put(\"expires_at\", expiresAt);\n        }\n        return params;\n    }\n}\n"
  },
  {
    "path": "cloudinary-core/src/main/java/com/cloudinary/AuthToken.java",
    "content": "package com.cloudinary;\n\nimport com.cloudinary.utils.ObjectUtils;\nimport com.cloudinary.utils.StringUtils;\n\nimport javax.crypto.Mac;\nimport javax.crypto.spec.SecretKeySpec;\nimport java.nio.charset.Charset;\nimport java.security.InvalidKeyException;\nimport java.security.NoSuchAlgorithmException;\nimport java.util.*;\nimport java.util.regex.Pattern;\n\n/**\n * Authentication Token generator\n */\npublic class AuthToken {\n    /**\n     * A null AuthToken, which can be passed to a method to override global settings.\n     */\n    public static final AuthToken NULL_AUTH_TOKEN = new AuthToken().setNull();\n    private static final String AUTH_TOKEN_NAME = \"__cld_token__\";\n\n    private String tokenName = AUTH_TOKEN_NAME;\n    private String key;\n    private long startTime;\n    private long expiration;\n    private String ip;\n    private List<String> acl = new ArrayList<>();\n    private long duration;\n    private boolean isNullToken = false;\n    private static final Pattern UNSAFE_URL_CHARS_PATTERN = Pattern.compile(\"[ \\\"#%&'/:;<=>?@\\\\[\\\\\\\\\\\\]^`{|}~]\");\n\n    public AuthToken() {\n    }\n\n    public AuthToken(String key) {\n        this.key = key;\n    }\n\n    /**\n     * Create a new AuthToken configuration.\n     *\n     * @param options The following keys may be used in the options: key, startTime, expiration, ip, acl, duration.\n     */\n    public AuthToken(Map options) {\n        if (options != null) {\n            this.tokenName = ObjectUtils.asString(options.get(\"tokenName\"), this.tokenName);\n            this.key = (String) options.get(\"key\");\n            this.startTime = ObjectUtils.asLong(options.get(\"startTime\"), 0L);\n            this.expiration = ObjectUtils.asLong(options.get(\"expiration\"), 0L);\n            this.ip = (String) options.get(\"ip\");\n\n            Object acl = options.get(\"acl\");\n            if (acl != null) {\n                if (acl instanceof String) {\n                    this.acl = Collections.singletonList(acl.toString());\n                } else if (Collection.class.isAssignableFrom(acl.getClass())) {\n                    this.acl = new ArrayList<String>((Collection<String>)acl);\n                }\n            }\n\n            this.duration = ObjectUtils.asLong(options.get(\"duration\"), 0L);\n        }\n    }\n\n    public Map<String,Object> asMap(){\n        Map<String,Object> result = new HashMap<String, Object>();\n\n        result.put(\"tokenName\", this.tokenName);\n        result.put(\"key\", this.key);\n        result.put(\"startTime\", this.startTime);\n        result.put(\"expiration\", this.expiration);\n        result.put(\"ip\", this.ip);\n        result.put(\"acl\", this.acl);\n        result.put(\"duration\", this.duration);\n\n        return result;\n    }\n\n    /**\n     * Create a new AuthToken configuration overriding the default token name.\n     *\n     * @param tokenName the name of the token. must be supported by the server.\n     * @return this\n     */\n    public AuthToken tokenName(String tokenName) {\n        this.tokenName = tokenName;\n        return this;\n    }\n\n    /**\n     * Set the start time of the token. Defaults to now.\n     *\n     * @param startTime in seconds since epoch\n     * @return this\n     */\n    public AuthToken startTime(long startTime) {\n        this.startTime = startTime;\n        return this;\n    }\n\n    /**\n     * Set the end time (expiration) of the token\n     *\n     * @param expiration in seconds since epoch\n     * @return this\n     */\n    public AuthToken expiration(long expiration) {\n        this.expiration = expiration;\n        return this;\n    }\n\n    /**\n     * Set the ip of the client\n     *\n     * @param ip\n     * @return this\n     */\n    public AuthToken ip(String ip) {\n        this.ip = ip;\n        return this;\n    }\n\n    /**\n     * Define an ACL for a cookie token\n     *\n     * @param acl\n     * @return this\n     */\n    public AuthToken acl(String... acl) {\n        this.acl = Arrays.asList(acl);\n        return this;\n    }\n\n    /**\n     * The duration of the token in seconds. This value is used to calculate the expiration of the token.\n     * It is ignored if expiration is provided.\n     *\n     * @param duration in seconds\n     * @return this\n     */\n    public AuthToken duration(long duration) {\n        this.duration = duration;\n        return this;\n    }\n\n    /**\n     * Generate the authentication token\n     *\n     * @return a signed token\n     */\n    public String generate() {\n        return generate(null);\n    }\n\n    /**\n     * Generate a URL token for the given URL.\n     *\n     * @param url the URL to be authorized\n     * @return a URL token\n     */\n    public String generate(String url) {\n\n        if (url == null && (acl == null || acl.size() == 0)) {\n            throw new IllegalArgumentException(\"Must provide acl or url\");\n        }\n\n        long expiration = this.expiration;\n        if (expiration == 0) {\n            if (duration > 0) {\n                final long start = startTime > 0 ? startTime : Calendar.getInstance(TimeZone.getTimeZone(\"UTC\")).getTimeInMillis() / 1000L;\n                expiration = start + duration;\n            } else {\n                throw new IllegalArgumentException(\"Must provide either expiration or duration\");\n            }\n        }\n        ArrayList<String> tokenParts = new ArrayList<String>();\n        if (ip != null) {\n            tokenParts.add(\"ip=\" + ip);\n        }\n        if (startTime > 0) {\n            tokenParts.add(\"st=\" + startTime);\n        }\n        tokenParts.add(\"exp=\" + expiration);\n        if (acl != null && acl.size() > 0) {\n            tokenParts.add(\"acl=\" + escapeToLower(String.join(\"!\", acl)));\n        }\n        ArrayList<String> toSign = new ArrayList<String>(tokenParts);\n        if (url != null && (acl == null || acl.size() == 0)) {\n            toSign.add(\"url=\" + escapeToLower(url));\n        }\n        String auth = digest(StringUtils.join(toSign, \"~\"));\n        tokenParts.add(\"hmac=\" + auth);\n        return tokenName + \"=\" + StringUtils.join(tokenParts, \"~\");\n\n    }\n\n    /**\n     * Escape url using lowercase hex code\n     *\n     * @param url a url string\n     * @return escaped url\n     */\n    private String escapeToLower(String url) {\n        String encodedUrl = StringUtils.urlEncode(url, UNSAFE_URL_CHARS_PATTERN, Charset.forName(\"UTF-8\"));\n        return encodedUrl;\n    }\n\n    /**\n     * Create a copy of this AuthToken\n     *\n     * @return a new AuthToken object\n     */\n    public AuthToken copy() {\n        final AuthToken authToken = new AuthToken(key);\n        authToken.tokenName = tokenName;\n        authToken.startTime = startTime;\n        authToken.expiration = expiration;\n        authToken.ip = ip;\n        authToken.acl = acl;\n        authToken.duration = duration;\n        return authToken;\n    }\n\n    /**\n     * Merge this token with another, creating a new token. Other's members who are not <code>null</code> or <code>0</code> will override this object's members.\n     *\n     * @param other the token to merge from\n     * @return a new token\n     */\n    public AuthToken merge(AuthToken other) {\n        if (other.equals(NULL_AUTH_TOKEN)) {\n            // NULL_AUTH_TOKEN can't merge\n            return other;\n        }\n        AuthToken merged = new AuthToken();\n        merged.key = other.key != null ? other.key : this.key;\n        merged.tokenName = other.tokenName != null ? other.tokenName : this.tokenName;\n        merged.startTime = other.startTime != 0 ? other.startTime : this.startTime;\n        merged.expiration = other.expiration != 0 ? other.expiration : this.expiration;\n        merged.ip = other.ip != null ? other.ip : this.ip;\n        merged.acl = other.acl != null ? other.acl : this.acl;\n        merged.duration = other.duration != 0 ? other.duration : this.duration;\n        return merged;\n    }\n\n    private String digest(String message) {\n        byte[] binKey = StringUtils.hexStringToByteArray(key);\n        try {\n            Mac hmac = Mac.getInstance(\"HmacSHA256\");\n            SecretKeySpec secret = new SecretKeySpec(binKey, \"HmacSHA256\");\n            hmac.init(secret);\n            final byte[] bytes = message.getBytes();\n            return StringUtils.encodeHexString(hmac.doFinal(bytes)).toLowerCase();\n        } catch (NoSuchAlgorithmException e) {\n            throw new RuntimeException(\"Cannot create authorization token.\", e);\n        } catch (InvalidKeyException e) {\n            throw new RuntimeException(\"Cannot create authorization token.\", e);\n        }\n    }\n\n    private AuthToken setNull() {\n        isNullToken = true;\n        return this;\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (o instanceof AuthToken) {\n            AuthToken other = (AuthToken) o;\n            return (isNullToken && other.isNullToken) ||\n                    (key == null ? other.key == null : key.equals(other.key)) &&\n                            tokenName.equals(other.tokenName) &&\n                            startTime == other.startTime &&\n                            expiration == other.expiration &&\n                            duration == other.duration &&\n                            (ip == null ? other.ip == null : ip.equals(other.ip)) &&\n                            (acl == null ? other.acl == null : acl.equals(other.acl));\n        } else {\n            return false;\n        }\n    }\n\n    @Override\n    public int hashCode() {\n        if (isNullToken) {\n            return 0;\n        } else {\n            return Arrays.asList(tokenName, startTime, expiration, duration, ip, acl).hashCode();\n        }\n    }\n}\n"
  },
  {
    "path": "cloudinary-core/src/main/java/com/cloudinary/BaseParam.java",
    "content": "package com.cloudinary;\n\nimport com.cloudinary.utils.StringUtils;\n\nimport java.util.List;\n\npublic class BaseParam {\n    private String param;\n\n    protected BaseParam(List<String> components) {\n        this.param = StringUtils.join(components, \":\");\n    }\n\n    protected BaseParam(String... components) {\n        this.param = StringUtils.join(components, \":\");\n    }\n\n    @Override\n    public String toString() {\n        return param;\n    }\n}\n"
  },
  {
    "path": "cloudinary-core/src/main/java/com/cloudinary/Cloudinary.java",
    "content": "package com.cloudinary;\n\nimport com.cloudinary.api.signing.ApiResponseSignatureVerifier;\nimport com.cloudinary.api.signing.NotificationRequestSignatureVerifier;\nimport com.cloudinary.strategies.AbstractApiStrategy;\nimport com.cloudinary.strategies.AbstractUploaderStrategy;\nimport com.cloudinary.strategies.StrategyLoader;\nimport com.cloudinary.utils.Analytics;\nimport com.cloudinary.utils.ObjectUtils;\nimport com.cloudinary.utils.StringUtils;\n\nimport java.io.IOException;\nimport java.io.UnsupportedEncodingException;\nimport java.net.URLEncoder;\nimport java.security.SecureRandom;\nimport java.util.*;\n\nimport static com.cloudinary.Util.buildMultiParams;\n\n@SuppressWarnings({\"rawtypes\", \"unchecked\"})\npublic class Cloudinary {\n\n    public static List<String> UPLOAD_STRATEGIES = new ArrayList<String>(Arrays.asList(\n            \"com.cloudinary.android.UploaderStrategy\",\n            \"com.cloudinary.http5.UploaderStrategy\"));\n    public static List<String> API_STRATEGIES = new ArrayList<String>(Arrays.asList(\n            \"com.cloudinary.android.ApiStrategy\",\n            \"com.cloudinary.http5.ApiStrategy\"));\n\n    public final static String CF_SHARED_CDN = \"d3jpl91pxevbkh.cloudfront.net\";\n    public final static String OLD_AKAMAI_SHARED_CDN = \"cloudinary-a.akamaihd.net\";\n    public final static String AKAMAI_SHARED_CDN = \"res.cloudinary.com\";\n    public final static String SHARED_CDN = AKAMAI_SHARED_CDN;\n\n    public final static String VERSION = \"2.3.1\";\n    static String USER_AGENT_PREFIX = \"CloudinaryJava\";\n    public final static String USER_AGENT_JAVA_VERSION = \"(Java \" + System.getProperty(\"java.version\") + \")\";\n\n    public final Configuration config;\n    private AbstractUploaderStrategy uploaderStrategy;\n    private AbstractApiStrategy apiStrategy;\n    private String userAgent = USER_AGENT_PREFIX+\"/\"+ VERSION + \" \"+USER_AGENT_JAVA_VERSION;\n    public Analytics analytics = new Analytics();\n    public Uploader uploader() {\n        return new Uploader(this, uploaderStrategy);\n    }\n\n    public Api api() {\n        return new Api(this, apiStrategy);\n    }\n\n    public Search search() {\n        return new Search(this);\n    }\n\n    public SearchFolders searchFolders() {\n        return new SearchFolders(this);\n    }\n\n    public static void registerUploaderStrategy(String className) {\n        if (!UPLOAD_STRATEGIES.contains(className)) {\n            UPLOAD_STRATEGIES.add(0, className);\n        }\n\n    }\n\n    public static void registerAPIStrategy(String className) {\n        if (!API_STRATEGIES.contains(className)) {\n            API_STRATEGIES.add(0, className);\n        }\n    }\n\n    private void loadStrategies() {\n        if (!this.config.loadStrategies) return;\n        uploaderStrategy = StrategyLoader.find(UPLOAD_STRATEGIES);\n\n        if (uploaderStrategy == null) {\n            throw new UnknownError(\"Can't find Cloudinary platform adapter [\" + StringUtils.join(UPLOAD_STRATEGIES, \",\") + \"]\");\n        }\n\n        apiStrategy = StrategyLoader.find(API_STRATEGIES);\n        if (apiStrategy == null) {\n            throw new UnknownError(\"Can't find Cloudinary platform adapter [\" + StringUtils.join(API_STRATEGIES, \",\") + \"]\");\n        }\n    }\n\n    public Cloudinary(Map config) {\n        this(new Configuration(config));\n    }\n\n    public Cloudinary(String cloudinaryUrl) {\n        this(Configuration.from(cloudinaryUrl));\n    }\n\n    public Cloudinary() {\n        this(System.getProperty(\"CLOUDINARY_URL\", System.getenv(\"CLOUDINARY_URL\")) != null\n                ? Configuration.from(System.getProperty(\"CLOUDINARY_URL\", System.getenv(\"CLOUDINARY_URL\")))\n                : new Configuration());\n    }\n\n    public Cloudinary(Configuration config) {\n        this.config = config;\n        loadStrategies();\n    }\n\n    public Url url() {\n        return new Url(this);\n    }\n\n    public String cloudinaryApiUrl(String action, Map options) {\n        String cloudinary = ObjectUtils.asString(options.get(\"upload_prefix\"),\n                ObjectUtils.asString(this.config.uploadPrefix, \"https://api.cloudinary.com\"));\n        String cloud_name = ObjectUtils.asString(options.get(\"cloud_name\"), ObjectUtils.asString(this.config.cloudName));\n        if (cloud_name == null)\n            throw new IllegalArgumentException(\"Must supply cloud_name in tag or in configuration\");\n        String resource_type = ObjectUtils.asString(options.get(\"resource_type\"), \"image\");\n        return StringUtils.join(new String[]{cloudinary, \"v1_1\", cloud_name, resource_type, action}, \"/\");\n    }\n\n    private final static SecureRandom RND = new SecureRandom();\n\n    public String randomPublicId() {\n        byte[] bytes = new byte[8];\n        RND.nextBytes(bytes);\n        return StringUtils.encodeHexString(bytes);\n    }\n\n    public String signedPreloadedImage(Map result) {\n        return result.get(\"resource_type\") + \"/upload/v\" + result.get(\"version\") + \"/\" + result.get(\"public_id\")\n                + (result.containsKey(\"format\") ? \".\" + result.get(\"format\") : \"\") + \"#\" + result.get(\"signature\");\n    }\n\n    public String apiSignRequest(Map<String, Object> paramsToSign, String apiSecret, int signatureVersion) {\n        return Util.produceSignature(paramsToSign, apiSecret, config.signatureAlgorithm, signatureVersion);\n    }\n\n    /**\n     * @return the userAgent that will be sent with every API call.\n     */\n    public String getUserAgent(){\n        return userAgent;\n    }\n\n    /**\n     * Set the prefix and version for the user agent that will be sent with every API call\n     * a userAgent is built from `prefix/version (additional data)`\n     * @param prefix - the prefix of the userAgent to be set\n     * @param version - the version of the userAgent to be set\n     */\n    public void setUserAgent(String prefix, String version){\n        userAgent = prefix+\"/\"+ version + \" (\"+USER_AGENT_PREFIX+ \" \"+VERSION+\") \" + USER_AGENT_JAVA_VERSION;\n    }\n\n    /**\n     * Set the analytics object that will be sent with every URL generation call.\n     * @param analytics - the analytics object to set\n     */\n    public void setAnalytics(Analytics analytics) {\n        this.analytics = analytics;\n    }\n\n    /**\n     * Verifies that Cloudinary notification request is genuine by checking its signature.\n     *\n     * Cloudinary can asynchronously process your e.g. image uploads requests. This is achieved by calling back API you\n     * specified during preparing of upload request as soon as it has been processed. See Upload Notifications in\n     * Cloudinary documentation for more details. In order to make sure it is Cloudinary calling your API back, hashed\n     * message authentication codes (HMAC's) based on agreed hashing function and configured Cloudinary API secret key\n     * are used for signing the requests.\n     *\n     * The following method serves as a convenient utility to perform the verification procedure.\n     *\n     * @param body Cloudinary Notification request body represented as string\n     * @param timestamp Cloudinary Notification request custom X-Cld-Timestamp HTTP header value\n     * @param signature Cloudinary Notification request custom X-Cld-Signature HTTP header value, i.e. the HMAC\n     * @param validFor desired period of request validity since issued, in seconds, for protection against replay attacks\n     * @return whether request signature is valid or not\n     */\n    public boolean verifyNotificationSignature(String body, String timestamp, String signature, long validFor) {\n        return new NotificationRequestSignatureVerifier(config.apiSecret, config.signatureAlgorithm).verifySignature(body, timestamp, signature, validFor);\n    }\n\n    /**\n     * Verifies that Cloudinary API response is genuine by checking its signature.\n     *\n     * Cloudinary can add a signature value in the response to API methods returning public id's and versions. In order\n     * to make sure it is genuine Cloudinary response, hashed message authentication codes (HMAC's) based on agreed hashing\n     * function and configured Cloudinary API secret key are used for signing the responses.\n     *\n     * The following method serves as a convenient utility to perform the verification procedure.\n     *\n     * @param publicId publicId response field value\n     * @param version version response field value\n     * @param signature signature response field value, i.e. the HMAC\n     * @return whether response signature is valid or not\n     */\n    public boolean verifyApiResponseSignature(String publicId, String version, String signature) {\n        return new ApiResponseSignatureVerifier(config.apiSecret, config.signatureAlgorithm).verifySignature(publicId, version, signature);\n    }\n\n    public void signRequest(Map<String, Object> params, Map<String, Object> options) {\n        String apiKey = ObjectUtils.asString(options.get(\"api_key\"), this.config.apiKey);\n        if (apiKey == null)\n            throw new IllegalArgumentException(\"Must supply api_key\");\n        String apiSecret = ObjectUtils.asString(options.get(\"api_secret\"), this.config.apiSecret);\n        if (apiSecret == null)\n            throw new IllegalArgumentException(\"Must supply api_secret\");\n        Util.clearEmpty(params);\n        params.put(\"signature\", this.apiSignRequest(params, apiSecret, this.config.signatureVersion));\n        params.put(\"api_key\", apiKey);\n    }\n\n    public String privateDownload(String publicId, String format, Map<String, Object> options) throws Exception {\n        Map<String, Object> params = new HashMap<String, Object>();\n        params.put(\"public_id\", publicId);\n        params.put(\"format\", format);\n        params.put(\"attachment\", options.get(\"attachment\"));\n        params.put(\"type\", options.get(\"type\"));\n        params.put(\"expires_at\", options.get(\"expires_at\"));\n        params.put(\"timestamp\", Util.timestamp());\n        signRequest(params, options);\n        return buildUrl(cloudinaryApiUrl(\"download\", options), params);\n    }\n\n    public String zipDownload(String tag, Map<String, Object> options) throws Exception {\n        Map<String, Object> params = new HashMap<String, Object>();\n        params.put(\"timestamp\", Util.timestamp());\n        params.put(\"tag\", tag);\n        Object transformation = options.get(\"transformation\");\n        if (transformation != null) {\n            if (transformation instanceof Transformation) {\n                transformation = ((Transformation) transformation).generate();\n            }\n            params.put(\"transformation\", transformation.toString());\n        }\n        params.put(\"transformation\", transformation);\n        signRequest(params, options);\n        return buildUrl(cloudinaryApiUrl(\"download_tag.zip\", options), params);\n    }\n\n    public String downloadArchive(Map<String, Object> options, String targetFormat) throws UnsupportedEncodingException {\n        Map params = Util.buildArchiveParams(options, targetFormat);\n        params.put(\"mode\", ArchiveParams.MODE_DOWNLOAD);\n        signRequest(params, options);\n        return buildUrl(cloudinaryApiUrl(\"generate_archive\", options), params);\n    }\n\n    public String downloadArchive(ArchiveParams params) throws UnsupportedEncodingException {\n        return downloadArchive(params.toMap(), params.targetFormat());\n    }\n\n    public String downloadZip(Map<String, Object> options) throws UnsupportedEncodingException {\n        return downloadArchive(options, \"zip\");\n    }\n\n    public String downloadGeneratedSprite(String tag, Map options) throws IOException {\n        if (StringUtils.isEmpty(tag)) throw new IllegalArgumentException(\"Tag cannot be empty\");\n\n        if (options == null)\n            options = new HashMap();\n\n        options.put(\"tag\", tag);\n        options.put(\"mode\", ArchiveParams.MODE_DOWNLOAD);\n\n        Map params = Util.buildGenerateSpriteParams(options);\n        signRequest(params, options);\n\n        return buildUrl(cloudinaryApiUrl(\"sprite\", options), params);\n    }\n\n    public String downloadGeneratedSprite(String[] urls, Map options) throws IOException {\n        if (urls.length < 1) throw new IllegalArgumentException(\"Request must contain at least one URL.\");\n        if (options == null)\n            options = new HashMap();\n\n        options.put(\"urls\", urls);\n        options.put(\"mode\", ArchiveParams.MODE_DOWNLOAD);\n\n        Map params = Util.buildGenerateSpriteParams(options);\n        signRequest(params, options);\n\n        return buildUrl(cloudinaryApiUrl(\"sprite\", options), params);\n    }\n\n    public String downloadMulti(String tag, Map options) throws IOException {\n        if (StringUtils.isEmpty(tag)) throw new IllegalArgumentException(\"Tag cannot be empty\");\n        if (options == null)\n            options = new HashMap();\n\n        options.put(\"tag\", tag);\n        options.put(\"mode\", ArchiveParams.MODE_DOWNLOAD);\n\n        Map params = buildMultiParams(options);\n        signRequest(params, options);\n\n        return buildUrl(cloudinaryApiUrl(\"multi\", options), params);\n    }\n\n    public String downloadMulti(String[] urls, Map options) throws IOException {\n        if (urls.length < 1) throw new IllegalArgumentException(\"Request must contain at least one URL.\");\n        if (options == null)\n            options = new HashMap();\n\n        options.put(\"urls\", urls);\n        options.put(\"mode\", ArchiveParams.MODE_DOWNLOAD);\n\n        Map params = buildMultiParams(options);\n        signRequest(params, options);\n\n        return buildUrl(cloudinaryApiUrl(\"multi\", options), params);\n    }\n\n    /**\n     * Generates URL for executing \"Download Folder\" operation on Cloudinary site.\n     * \n     * @param folderPath path of folder to generate download URL for\n     * @param options    optional, holds hints for URL generation procedure, see documentation for full list\n     * @return generated URL for downloading specified folder as ZIP archive\n     */\n    public String downloadFolder(String folderPath, Map options) throws UnsupportedEncodingException {\n        if (StringUtils.isEmpty(folderPath)) {\n            throw new IllegalArgumentException(\"Folder path parameter value is required\");\n        }\n\n        Map adjustedOptions = new HashMap();\n        if (options != null) {\n            adjustedOptions.putAll(options);\n        }\n\n        adjustedOptions.put(\"prefixes\", folderPath);\n\n        final Object resourceType = adjustedOptions.get(\"resource_type\");\n        adjustedOptions.put(\"resource_type\", resourceType != null ? resourceType : \"all\");\n\n        return downloadArchive(adjustedOptions, (String) adjustedOptions.get(\"target_format\"));\n    }\n\n    /**\n     * Returns an URL of a specific version of a backed up asset that can be used to download that\n     * version of the asset (within an hour of the request).\n     *\n     * @param assetId   The identifier of the uploaded asset.\n     * @param versionId The identifier of a backed up version of the asset.\n     * @param options   Optional, holds hints for URL generation procedure, see documentation for\n     *                  full list\n     * @return          The download URL of the asset\n     */\n    public String downloadBackedupAsset(String assetId, String versionId, Map options) throws UnsupportedEncodingException {\n        if (StringUtils.isEmpty(assetId)) {\n            throw new IllegalArgumentException(\"AssetId parameter is required\");\n        }\n\n        if (StringUtils.isEmpty(versionId)) {\n            throw new IllegalArgumentException(\"VersionId parameter is required\");\n        }\n\n        Map<String, Object> params = new HashMap<String, Object>();\n        params.put(\"asset_id\", assetId);\n        params.put(\"version_id\", versionId);\n        params.put(\"timestamp\", Util.timestamp());\n\n        signRequest(params, options);\n        return buildUrl(cloudinaryApiUrl(\"download_backup\", options), params);\n    }\n\n    private String buildUrl(String base, Map<String, Object> params) throws UnsupportedEncodingException {\n        StringBuilder urlBuilder = new StringBuilder();\n        urlBuilder.append(base);\n        if (!params.isEmpty()) {\n            urlBuilder.append(\"?\");\n        }\n        boolean first = true;\n        for (Map.Entry<String, Object> param : params.entrySet()) {\n            if (param.getValue() == null) continue;\n\n            String keyValue = null;\n            Object value = param.getValue();\n            if (!first) urlBuilder.append(\"&\");\n            if (value instanceof Object[])\n                value = Arrays.asList(value);\n            if (value instanceof Collection) {\n                String key = param.getKey() + \"[]=\";\n                Collection<Object> items = (Collection) value;\n                List<String> encodedItems = new ArrayList<String>();\n                for (Object item : items)\n                    encodedItems.add(URLEncoder.encode(item.toString(), \"UTF-8\"));\n                keyValue = key + StringUtils.join(encodedItems, \"&\" + key);\n            } else {\n                keyValue = param.getKey() + \"=\" +\n                        URLEncoder.encode(value.toString(), \"UTF-8\");\n            }\n            urlBuilder.append(keyValue);\n            first = false;\n        }\n        return urlBuilder.toString();\n    }\n}\n"
  },
  {
    "path": "cloudinary-core/src/main/java/com/cloudinary/Configuration.java",
    "content": "package com.cloudinary;\n\nimport java.io.UnsupportedEncodingException;\nimport java.net.URI;\nimport java.net.URLDecoder;\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport com.cloudinary.utils.ObjectUtils;\nimport com.cloudinary.utils.StringUtils;\n\n/**\n * Configuration object for a {@link Cloudinary} instance\n */\npublic class Configuration {\n    public final static String CF_SHARED_CDN = \"d3jpl91pxevbkh.cloudfront.net\";\n    public final static String OLD_AKAMAI_SHARED_CDN = \"cloudinary-a.akamaihd.net\";\n    public final static String AKAMAI_SHARED_CDN = \"res.cloudinary.com\";\n    public final static String SHARED_CDN = AKAMAI_SHARED_CDN;\n    public final static String VERSION = \"1.0.2\";\n    public final static String USER_AGENT = \"cld-android-\" + VERSION;\n    public static final boolean DEFAULT_IS_LONG_SIGNATURE = false;\n    public static final SignatureAlgorithm DEFAULT_SIGNATURE_ALGORITHM = SignatureAlgorithm.SHA1;\n    public static final int DEFAULT_SIGNATURE_VERSION = 2;\n\n    private static final String CONFIG_PROP_SIGNATURE_ALGORITHM = \"signature_algorithm\";\n\n    public String cloudName;\n    public String apiKey;\n    public String apiSecret;\n    public String secureDistribution;\n    public String cname;\n    public String uploadPrefix;\n    public boolean secure;\n    public boolean privateCdn;\n    public boolean cdnSubdomain;\n    public boolean shorten;\n    public String callback;\n    public String proxyHost;\n    public int proxyPort;\n    public Map<String, Object> properties = new HashMap<String, Object>();\n    public Boolean secureCdnSubdomain;\n    public boolean useRootPath;\n    public boolean useFetchFormat;\n    public int timeout;\n    public boolean loadStrategies = true;\n    public boolean clientHints = false;\n    public AuthToken authToken;\n    public boolean forceVersion = true;\n    public boolean longUrlSignature = DEFAULT_IS_LONG_SIGNATURE;\n    public SignatureAlgorithm signatureAlgorithm = DEFAULT_SIGNATURE_ALGORITHM;\n    public int signatureVersion = DEFAULT_SIGNATURE_VERSION;\n    public String oauthToken = null;\n    public Boolean analytics;\n    public Configuration() {\n    }\n\n    private Configuration(\n            String cloudName,\n            String apiKey,\n            String apiSecret,\n            String secureDistribution,\n            String cname,\n            String uploadPrefix,\n            boolean secure,\n            boolean privateCdn,\n            boolean cdnSubdomain,\n            boolean shorten,\n            String callback,\n            String proxyHost,\n            int proxyPort,\n            Boolean secureCdnSubdomain,\n            boolean useRootPath,\n            boolean useFetchFormat,\n            int timeout,\n            boolean loadStrategies,\n            boolean forceVersion,\n            boolean longUrlSignature,\n            SignatureAlgorithm signatureAlgorithm,\n            int signatureVersion,\n            String oauthToken,\n            boolean analytics) {\n        this.cloudName = cloudName;\n        this.apiKey = apiKey;\n        this.apiSecret = apiSecret;\n        this.secureDistribution = secureDistribution;\n        this.cname = cname;\n        this.uploadPrefix = uploadPrefix;\n        this.secure = secure;\n        this.privateCdn = privateCdn;\n        this.cdnSubdomain = cdnSubdomain;\n        this.shorten = shorten;\n        this.callback = callback;\n        this.proxyHost = proxyHost;\n        this.proxyPort = proxyPort;\n        this.secureCdnSubdomain = secureCdnSubdomain;\n        this.useRootPath = useRootPath;\n        this.useFetchFormat = useFetchFormat;\n        this.timeout = timeout;\n        this.loadStrategies = loadStrategies;\n        this.forceVersion = forceVersion;\n        this.longUrlSignature = longUrlSignature;\n        this.signatureAlgorithm = signatureAlgorithm;\n        this.signatureVersion = signatureVersion;\n        this.oauthToken = oauthToken;\n        this.analytics = analytics;\n    }\n\n    @SuppressWarnings(\"rawtypes\")\n    public Configuration(Map config) {\n        update(config);\n    }\n\n    @SuppressWarnings(\"rawtypes\")\n    public void update(Map config) {\n        this.cloudName = (String) config.get(\"cloud_name\");\n        this.apiKey = (String) config.get(\"api_key\");\n        this.apiSecret = (String) config.get(\"api_secret\");\n        this.secureDistribution = (String) config.get(\"secure_distribution\");\n        this.cname = (String) config.get(\"cname\");\n        this.secure = ObjectUtils.asBoolean(config.get(\"secure\"), true);\n        this.privateCdn = ObjectUtils.asBoolean(config.get(\"private_cdn\"), false);\n        this.cdnSubdomain = ObjectUtils.asBoolean(config.get(\"cdn_subdomain\"), false);\n        this.shorten = ObjectUtils.asBoolean(config.get(\"shorten\"), false);\n        this.uploadPrefix = (String) config.get(\"upload_prefix\");\n        this.callback = (String) config.get(\"callback\");\n        this.proxyHost = (String) config.get(\"proxy_host\");\n        this.proxyPort = ObjectUtils.asInteger(config.get(\"proxy_port\"), 0);\n        this.secureCdnSubdomain = ObjectUtils.asBoolean(config.get(\"secure_cdn_subdomain\"), null);\n        this.useRootPath = ObjectUtils.asBoolean(config.get(\"use_root_path\"), false);\n        this.useFetchFormat = ObjectUtils.asBoolean(config.get(\"use_fetch_format\"), false);\n        this.loadStrategies = ObjectUtils.asBoolean(config.get(\"load_strategies\"), true);\n        this.timeout = ObjectUtils.asInteger(config.get(\"timeout\"), 0);\n        this.clientHints = ObjectUtils.asBoolean(config.get(\"client_hints\"), false);\n        this.analytics = ObjectUtils.asBoolean(config.get(\"analytics\"), true);\n        Map tokenMap = (Map) config.get(\"auth_token\");\n        if (tokenMap != null) {\n            this.authToken = new AuthToken(tokenMap);\n        }\n        this.forceVersion = ObjectUtils.asBoolean(config.get(\"force_version\"), true);\n        Map properties = (Map) config.get(\"properties\");\n        if (properties != null) {\n            this.properties.putAll(properties);\n        }\n        this.longUrlSignature = ObjectUtils.asBoolean(config.get(\"long_url_signature\"), DEFAULT_IS_LONG_SIGNATURE);\n        this.signatureAlgorithm = SignatureAlgorithm.valueOf(ObjectUtils.asString(config.get(CONFIG_PROP_SIGNATURE_ALGORITHM), DEFAULT_SIGNATURE_ALGORITHM.name()));\n        this.signatureVersion = ObjectUtils.asInteger(config.get(\"signature_version\"), DEFAULT_SIGNATURE_VERSION);\n        this.oauthToken = (String) config.get(\"oauth_token\");\n\n    }\n\n    @SuppressWarnings(\"rawtypes\")\n    public Map<String, Object> asMap() {\n        Map<String, Object> map = new HashMap<String, Object>();\n        map.put(\"cloud_name\", cloudName);\n        map.put(\"api_key\", apiKey);\n        map.put(\"api_secret\", apiSecret);\n        map.put(\"secure_distribution\", secureDistribution);\n        map.put(\"cname\", cname);\n        map.put(\"secure\", secure);\n        map.put(\"private_cdn\", privateCdn);\n        map.put(\"cdn_subdomain\", cdnSubdomain);\n        map.put(\"shorten\", shorten);\n        map.put(\"upload_prefix\", uploadPrefix);\n        map.put(\"callback\", callback);\n        map.put(\"proxy_host\", proxyHost);\n        map.put(\"proxy_port\", proxyPort);\n        map.put(\"secure_cdn_subdomain\", secureCdnSubdomain);\n        map.put(\"use_root_path\", useRootPath);\n        map.put(\"use_fetch_format\", useFetchFormat);\n        map.put(\"load_strategies\", loadStrategies);\n        map.put(\"timeout\", timeout);\n        map.put(\"client_hints\", clientHints);\n        if (authToken != null) {\n            map.put(\"auth_token\", authToken.asMap());\n        }\n        map.put(\"force_version\", forceVersion);\n        map.put(\"properties\", new HashMap<String,Object>(properties));\n        map.put(\"long_url_signature\", longUrlSignature);\n        map.put(CONFIG_PROP_SIGNATURE_ALGORITHM, signatureAlgorithm.toString());\n        map.put(\"signature_version\", signatureVersion);\n        map.put(\"oauth_token\", oauthToken);\n        map.put(\"analytics\", analytics);\n        return map;\n    }\n\n\n    public Configuration(Configuration other) {\n        this.cloudName = other.cloudName;\n        this.apiKey = other.apiKey;\n        this.apiSecret = other.apiSecret;\n        this.secureDistribution = other.secureDistribution;\n        this.cname = other.cname;\n        this.uploadPrefix = other.uploadPrefix;\n        this.secure = other.secure;\n        this.privateCdn = other.privateCdn;\n        this.cdnSubdomain = other.cdnSubdomain;\n        this.shorten = other.shorten;\n        this.callback = other.callback;\n        this.proxyHost = other.proxyHost;\n        this.proxyPort = other.proxyPort;\n        this.secureCdnSubdomain = other.secureCdnSubdomain;\n        this.useRootPath = other.useRootPath;\n        this.useFetchFormat = other.useFetchFormat;\n        this.timeout = other.timeout;\n        this.clientHints = other.clientHints;\n        if (other.authToken != null) {\n            this.authToken = other.authToken.copy();\n        }\n        this.forceVersion = other.forceVersion;\n        this.loadStrategies = other.loadStrategies;\n        this.properties.putAll(other.properties);\n        this.longUrlSignature = other.longUrlSignature;\n        this.signatureAlgorithm = other.signatureAlgorithm;\n        this.signatureVersion = other.signatureVersion;\n        this.oauthToken = other.oauthToken;\n        this.analytics = other.analytics;\n    }\n\n    /**\n     * Create a new Configuration from an existing one\n     *\n     * @param other\n     * @return a new configuration with the arguments supplied by another configuration object\n     */\n    public static Configuration from(Configuration other) {\n        return new Builder().from(other).build();\n    }\n\n    /**\n     * Create a Configuration from a cloudinary url\n     * <p>\n     * Example url: cloudinary://123456789012345:abcdeghijklmnopqrstuvwxyz12@n07t21i7\n     *\n     * @param cloudinaryUrl configuration url\n     * @return a new configuration with the arguments supplied by the url\n     */\n    public static Configuration from(String cloudinaryUrl) {\n        Configuration config = new Configuration();\n        config.update(parseConfigUrl(cloudinaryUrl));\n        return config;\n    }\n\n\n    static protected Map parseConfigUrl(String cloudinaryUrl) {\n        Map params = new HashMap();\n        URI cloudinaryUri = URI.create(cloudinaryUrl);\n        if (cloudinaryUri.getScheme() == null || !cloudinaryUri.getScheme().equalsIgnoreCase(\"cloudinary\")){\n            throw new IllegalArgumentException(\"Invalid CLOUDINARY_URL scheme. Expecting to start with 'cloudinary://'\");\n        }\n        params.put(\"cloud_name\", cloudinaryUri.getHost());\n        if (cloudinaryUri.getUserInfo() != null) {\n            String[] creds = cloudinaryUri.getUserInfo().split(\":\");\n            params.put(\"api_key\", creds[0]);\n            if (creds.length > 1) {\n                params.put(\"api_secret\", creds[1]);\n            }\n        }\n        params.put(\"private_cdn\", !StringUtils.isEmpty(cloudinaryUri.getPath()));\n        params.put(\"secure_distribution\", cloudinaryUri.getPath());\n        updateMapfromURI(params, cloudinaryUri);\n        return params;\n    }\n\n    static private void updateMapfromURI(Map params, URI cloudinaryUri) {\n        if (cloudinaryUri.getQuery() != null) {\n            for (String param : cloudinaryUri.getQuery().split(\"&\")) {\n                String[] keyValue = param.split(\"=\");\n                try {\n                    final String value = URLDecoder.decode(keyValue[1], \"ASCII\");\n                    final String key = keyValue[0];\n                    if(isNestedKey(key)) {\n                        putNestedValue(params, key, value);\n                    } else {\n                        params.put(key, value);\n                    }\n                } catch (UnsupportedEncodingException e) {\n                    throw new RuntimeException(\"Unexpected exception\", e);\n                }\n            }\n        }\n    }\n\n    static private void putNestedValue(Map params, String key, String value) {\n        String[] chain = key.split(\"[\\\\[\\\\]]+\");\n        Map outer = params;\n        String innerKey = chain[0];\n        for (int i = 0; i < chain.length -1; i++, innerKey = chain[i]) {\n            Map inner = (Map) outer.get(innerKey);\n            if (inner == null) {\n                inner = new HashMap();\n                outer.put(innerKey, inner);\n            }\n            outer = inner;\n        }\n        outer.put(innerKey, value);\n    }\n\n    static private boolean isNestedKey(String key) {\n        return key.matches(\"\\\\w+\\\\[\\\\w+\\\\]\");\n    }\n\n    /**\n     * Build a new {@link Configuration}\n     */\n    public static class Builder {\n        private String cloudName;\n        private String apiKey;\n        private String apiSecret;\n        private String secureDistribution;\n        private String cname;\n        private String uploadPrefix;\n        private boolean secure;\n        private boolean privateCdn;\n        private boolean cdnSubdomain;\n        private boolean shorten;\n        private String callback;\n        private String proxyHost;\n        private int proxyPort;\n        private Boolean secureCdnSubdomain;\n        private boolean useRootPath;\n        private boolean useFetchFormat;\n        private boolean loadStrategies = true;\n        private int timeout;\n        private boolean clientHints = false;\n        private AuthToken authToken;\n        private boolean forceVersion = true;\n        private boolean longUrlSignature = DEFAULT_IS_LONG_SIGNATURE;\n        private SignatureAlgorithm signatureAlgorithm = DEFAULT_SIGNATURE_ALGORITHM;\n        private int signatureVersion = DEFAULT_SIGNATURE_VERSION;\n        private String oauthToken = null;\n        private boolean analytics;\n\n        /**\n         * Set the HTTP connection timeout.\n         *\n         * @param timeout time in seconds, or 0 to use the default platform value\n         * @return builder for chaining\n         */\n        public Builder setTimeout(int timeout) {\n            this.timeout = timeout;\n            return this;\n        }\n\n        /**\n         * Creates a {@link Configuration} with the arguments supplied to this builder\n         */\n        public Configuration build() {\n            final Configuration configuration = new Configuration(\n                            cloudName,\n                            apiKey,\n                            apiSecret,\n                            secureDistribution,\n                            cname,\n                            uploadPrefix,\n                            secure,\n                            privateCdn,\n                            cdnSubdomain,\n                            shorten,\n                            callback,\n                            proxyHost,\n                            proxyPort,\n                            secureCdnSubdomain,\n                            useRootPath,\n                            useFetchFormat,\n                            timeout,\n                            loadStrategies,\n                            forceVersion,\n                            longUrlSignature,\n                            signatureAlgorithm,\n                            signatureVersion,\n                            oauthToken,\n                            analytics);\n            configuration.clientHints = clientHints;\n            return configuration;\n        }\n\n        /**\n         * The unique name of your cloud at Cloudinary\n         * You can find your cloud name in the Account Details section in the dashboard of Cloudinary Management Console.\n         */\n        public Builder setCloudName(String cloudName) {\n            this.cloudName = cloudName;\n            return this;\n        }\n\n        /**\n         * API Key\n         * You can find API Key in the Account Details section in the dashboard of Cloudinary Management Console.\n         */\n        public Builder setApiKey(String apiKey) {\n            this.apiKey = apiKey;\n            return this;\n        }\n\n        /**\n         * API Secret\n         * You can find API Secret in the Account Details section in the dashboard of Cloudinary Management Console.\n         */\n        public Builder setApiSecret(String apiSecret) {\n            this.apiSecret = apiSecret;\n            return this;\n        }\n\n        /**\n         * The domain name of the CDN distribution to use for building HTTPS URLs.\n         * Relevant only for Advanced plan's users that have a private CDN distribution.\n         */\n        public Builder setSecureDistribution(String secureDistribution) {\n            this.secureDistribution = secureDistribution;\n            return this;\n        }\n\n        /**\n         * Custom domain name to use for building HTTP URLs.\n         * Relevant only for Advanced plan's users that have a private CDN distribution and a custom CNAME.\n         */\n        public Builder setCname(String cname) {\n            this.cname = cname;\n            return this;\n        }\n\n        /**\n         * Force HTTPS URLs of images even if embedded in non-secure HTTP pages.\n         */\n        public Builder setSecure(boolean secure) {\n            this.secure = secure;\n            return this;\n        }\n\n        /**\n         * Should be set to true for Advanced plan's users that have a private CDN distribution.\n         */\n        public Builder setPrivateCdn(boolean privateCdn) {\n            this.privateCdn = privateCdn;\n            return this;\n        }\n\n        public Builder setSecureCdnSubdomain(Boolean secureCdnSubdomain) {\n            this.secureCdnSubdomain = secureCdnSubdomain;\n            return this;\n        }\n\n\n        /**\n         * Whether to automatically build URLs with multiple CDN sub-domains.\n         */\n        public Builder setCdnSubdomain(boolean cdnSubdomain) {\n            this.cdnSubdomain = cdnSubdomain;\n            return this;\n        }\n\n        public Builder setShorten(boolean shorten) {\n            this.shorten = shorten;\n            return this;\n        }\n\n        public Builder setCallback(String callback) {\n            this.callback = callback;\n            return this;\n        }\n\n        public Builder setUploadPrefix(String uploadPrefix) {\n            this.uploadPrefix = uploadPrefix;\n            return this;\n        }\n\n        public Builder setUseRootPath(boolean useRootPath) {\n            this.useRootPath = useRootPath;\n            return this;\n        }\n\n        public Builder setUseFetchFormat(boolean useFetchFormat) {\n            this.useFetchFormat = useFetchFormat;\n            return this;\n        }\n\n        public Builder setLoadStrategies(boolean loadStrategies) {\n            this.loadStrategies = loadStrategies;\n            return this;\n        }\n\n        public Builder setAnalytics(boolean analytics) {\n            this.analytics = analytics;\n            return this;\n        }\n\n        public Builder setClientHints(boolean clientHints) {\n            this.clientHints = clientHints;\n            return this;\n        }\n\n        public Builder setAuthToken(AuthToken authToken) {\n            this.authToken = authToken;\n            return this;\n        }\n        public Builder setForceVersion(boolean forceVersion) {\n            this.forceVersion = forceVersion;\n            return this;\n        }\n\n        public Builder setIsLongUrlSignature(boolean isLong) {\n            this.longUrlSignature = isLong;\n            return this;\n        }\n\n        public Builder setSignatureAlgorithm(SignatureAlgorithm signatureAlgorithm) {\n            this.signatureAlgorithm = signatureAlgorithm;\n            return this;\n        }\n\n        public Builder setSignatureVersion(int signatureVersion) {\n            this.signatureVersion = signatureVersion;\n            return this;\n        }\n\n        public Builder setOAuthToken(String oauthToken) {\n            this.oauthToken = oauthToken;\n            return this;\n        }\n\n        /**\n         * Initialize builder from existing {@link Configuration}\n         *\n         * @param other a different configuration object\n         * @return an initialized builder configured with <code>other</code>\n         */\n        public Builder from(Configuration other) {\n            this.cloudName = other.cloudName;\n            this.apiKey = other.apiKey;\n            this.apiSecret = other.apiSecret;\n            this.secureDistribution = other.secureDistribution;\n            this.cname = other.cname;\n            this.uploadPrefix = other.uploadPrefix;\n            this.secure = other.secure;\n            this.privateCdn = other.privateCdn;\n            this.cdnSubdomain = other.cdnSubdomain;\n            this.shorten = other.shorten;\n            this.callback = other.callback;\n            this.proxyHost = other.proxyHost;\n            this.proxyPort = other.proxyPort;\n            this.secureCdnSubdomain = other.secureCdnSubdomain;\n            this.useRootPath = other.useRootPath;\n            this.useFetchFormat = other.useFetchFormat;\n            this.loadStrategies = other.loadStrategies;\n            this.timeout = other.timeout;\n            this.clientHints = other.clientHints;\n            this.authToken = other.authToken == null ? null : other.authToken.copy();\n            this.forceVersion = other.forceVersion;\n            this.longUrlSignature = other.longUrlSignature;\n            this.signatureAlgorithm = other.signatureAlgorithm;\n            this.signatureVersion = other.signatureVersion;\n            this.oauthToken = other.oauthToken;\n            this.analytics = other.analytics;\n            return this;\n        }\n    }\n}\n"
  },
  {
    "path": "cloudinary-core/src/main/java/com/cloudinary/Coordinates.java",
    "content": "package com.cloudinary;\n\nimport java.io.Serializable;\nimport java.util.ArrayList;\nimport java.util.Collection;\n\nimport com.cloudinary.utils.Rectangle;\nimport com.cloudinary.utils.StringUtils;\n\npublic class Coordinates implements Serializable{\n\n    Collection<Rectangle> coordinates = new ArrayList<Rectangle>();\n\n    public Coordinates() {\n    }\n\n    public Coordinates(Collection<Rectangle> coordinates) {\n        this.coordinates = coordinates;\n    }\n\n    public Coordinates(int[] rect) {\n        Collection<Rectangle> coordinates = new ArrayList<Rectangle>();\n        if (rect.length != 4) {\n            throw new IllegalArgumentException(\"Must supply exactly 4 values for coordinates (x,y,width,height)\");\n        }\n        coordinates.add(new Rectangle(rect[0], rect[1], rect[2], rect[3]));\n        this.coordinates = coordinates;\n    }\n\n    public Coordinates(Rectangle rect) {\n        Collection<Rectangle> coordinates = new ArrayList<Rectangle>();\n        coordinates.add(rect);\n        this.coordinates = coordinates;\n    }\n\n    public Coordinates(String stringCoords) throws IllegalArgumentException {\n        Collection<Rectangle> coordinates = new ArrayList<Rectangle>();\n        for (String stringRect : stringCoords.split(\"\\\\|\")) {\n            if (StringUtils.isEmpty(stringRect))\n                continue;\n            String[] elements = stringRect.split(\",\");\n            if (elements.length != 4) {\n                throw new IllegalArgumentException(String.format(\"Must supply exactly 4 values for coordinates (x,y,width,height) %d supplied: %s\",\n                        elements.length, stringRect));\n            }\n            coordinates.add(new Rectangle(Integer.parseInt(elements[0]), Integer.parseInt(elements[1]), Integer.parseInt(elements[2]), Integer\n                    .parseInt(elements[3])));\n        }\n        this.coordinates = coordinates;\n    }\n\n    public static Coordinates parseCoordinates(Object coordinates) throws IllegalArgumentException {\n        if (coordinates instanceof Coordinates) {\n            return (Coordinates) coordinates;\n        } else if (coordinates instanceof int[]) {\n            return new Coordinates((int[]) coordinates);\n        } else if (coordinates instanceof Rectangle) {\n            return new Coordinates((Rectangle) coordinates);\n        } else {\n            return new Coordinates(coordinates.toString());\n        }\n    }\n\n    public void addRect(Rectangle rect) {\n        this.coordinates.add(rect);\n    }\n\n    public Collection<Rectangle> underlaying() {\n        return this.coordinates;\n    }\n\n    @Override\n    public String toString() {\n        ArrayList<String> rects = new ArrayList<String>();\n        for (Rectangle rect : this.coordinates) {\n            rects.add(rect.x + \",\" + rect.y + \",\" + rect.width + \",\" + rect.height);\n        }\n        return StringUtils.join(rects, \"|\");\n    }\n\n}\n"
  },
  {
    "path": "cloudinary-core/src/main/java/com/cloudinary/CustomFunction.java",
    "content": "package com.cloudinary;\n\nimport com.cloudinary.utils.Base64Coder;\n\n/**\n * Helper class to generate a custom function params to be used in {@link Transformation#customFunction(CustomFunction)}.\n */\npublic class CustomFunction extends BaseParam{\n\n    private CustomFunction(String... components) {\n        super(components);\n    }\n\n    /**\n     * Generate a web-assembly custom action param to send to {@link Transformation#customFunction(CustomFunction)}\n     * @param publicId The public id of the web-assembly file\n     * @return A new instance of custom action param\n     */\n    public static CustomFunction wasm(String publicId){\n        return new CustomFunction(\"wasm\", publicId);\n    }\n\n    /**\n     * Generate a remote lambda custom action param to send to {@link Transformation#customFunction(CustomFunction)}\n     * @param url   The public url of the aws lambda function\n     * @return A new instance of custom action param\n     */\n    public static CustomFunction remote(String url){\n        return new CustomFunction(\"remote\", Base64Coder.encodeURLSafeString(url));\n    }\n}\n"
  },
  {
    "path": "cloudinary-core/src/main/java/com/cloudinary/EagerTransformation.java",
    "content": "package com.cloudinary;\n\nimport com.cloudinary.utils.StringUtils;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\n\npublic class EagerTransformation extends Transformation<EagerTransformation> {\n    protected String format;\n\n    @SuppressWarnings(\"rawtypes\")\n    public EagerTransformation(List<Map> transformations) {\n        super(transformations);\n    }\n\n    public EagerTransformation() {\n        super();\n    }\n\n    public EagerTransformation format(String format) {\n        this.format = format;\n        return this;\n    }\n\n    public String getFormat() {\n        return format;\n    }\n\n    @Override\n    public String generate(Iterable<Map> optionsList) {\n        List<String> components = new ArrayList<String>();\n        for (Map options : optionsList) {\n            if (options.size() > 0) {\n                components.add(super.generate(options));\n            }\n        }\n\n        if (format != null){\n            components.add(format);\n        }\n\n        return StringUtils.join(components, \"/\");\n    }\n\n    @Override\n    public String generate(Map options) {\n        List<String> eager = new ArrayList<String>();\n        eager.add(super.generate(options));\n\n        if (format != null){\n            eager.add(format);\n        }\n\n        return StringUtils.join(eager, \"/\");\n    }\n}\n"
  },
  {
    "path": "cloudinary-core/src/main/java/com/cloudinary/ProgressCallback.java",
    "content": "package com.cloudinary;\n\n/**\n * Defines a callback for network operations.\n */\npublic interface ProgressCallback {\n    /**\n     * Invoked during network operation.\n     * @param bytesUploaded the number of bytes uploaded so far\n     * @param totalBytes the total number of byte to upload - if known\n     */\n    void onProgress(long bytesUploaded, long totalBytes);\n}\n"
  },
  {
    "path": "cloudinary-core/src/main/java/com/cloudinary/ResponsiveBreakpoint.java",
    "content": "package com.cloudinary;\n\nimport org.cloudinary.json.JSONObject;\n\npublic class ResponsiveBreakpoint extends JSONObject {\n    public ResponsiveBreakpoint() {\n        put(\"create_derived\", true);\n    }\n\n    public boolean isCreateDerived() {\n        return optBoolean(\"create_derived\");\n    }\n\n    public ResponsiveBreakpoint createDerived(boolean createDerived) {\n        put(\"create_derived\", createDerived);\n        return this;\n    }\n\n    public Transformation transformation() {\n        return (Transformation) opt(\"transformation\");\n    }\n\n    public ResponsiveBreakpoint transformation(Transformation transformation) {\n        put(\"transformation\", transformation);\n        return this;\n    }\n\n    public ResponsiveBreakpoint format(String format) {\n        put(\"format\", format);\n        return this;\n    }\n\n    public String format() {\n        return optString(\"format\");\n    }\n\n    public int maxWidth() {\n        return optInt(\"max_width\");\n    }\n\n    public ResponsiveBreakpoint maxWidth(int maxWidth) {\n        put(\"max_width\", maxWidth);\n        return this;\n    }\n\n    public int minWidth() {\n        return optInt(\"min_width\");\n    }\n\n    public ResponsiveBreakpoint minWidth(Integer minWidth) {\n        put(\"min_width\", minWidth);\n        return this;\n    }\n\n    public int bytesStep() {\n        return optInt(\"bytes_step\");\n    }\n\n    public ResponsiveBreakpoint bytesStep(Integer bytesStep) {\n        put(\"bytes_step\", bytesStep);\n        return this;\n    }\n\n    public int maxImages() {\n        return optInt(\"max_images\");\n    }\n\n    public ResponsiveBreakpoint maxImages(Integer maxImages) {\n        put(\"max_images\", maxImages);\n        return this;\n    }\n}\n"
  },
  {
    "path": "cloudinary-core/src/main/java/com/cloudinary/Search.java",
    "content": "package com.cloudinary;\n\nimport com.cloudinary.api.ApiResponse;\nimport com.cloudinary.utils.Base64Coder;\nimport com.cloudinary.utils.ObjectUtils;\nimport com.cloudinary.utils.StringUtils;\nimport org.cloudinary.json.JSONObject;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.HashMap;\nimport java.util.Map;\n\npublic class Search {\n\n    protected final Api api;\n    private ArrayList<HashMap<String, Object>> sortByParam;\n    private ArrayList<String> aggregateParam;\n    private ArrayList<String> withFieldParam;\n    private HashMap<String, Object> params;\n    private ArrayList<String> fields;\n\n    private int ttl = 300;\n\n    Search(Cloudinary cloudinary) {\n        this.api = cloudinary.api();\n        this.params = new HashMap<String, Object>();\n        this.sortByParam = new ArrayList<HashMap<String, Object>>();\n        this.aggregateParam = new ArrayList<String>();\n        this.withFieldParam = new ArrayList<String>();\n        this.fields = new ArrayList<String>();\n    }\n\n    public Search ttl(int ttl) {\n        this.ttl = ttl;\n        return this;\n    }\n    public Search expression(String value) {\n        this.params.put(\"expression\", value);\n        return this;\n    }\n\n    public Search maxResults(Integer value) {\n        this.params.put(\"max_results\", value);\n        return this;\n    }\n\n    public Search nextCursor(String value) {\n        this.params.put(\"next_cursor\", value);\n        return this;\n    }\n\n    public Search aggregate(String field) {\n        if (!aggregateParam.contains(field)) {\n            aggregateParam.add(field);\n        }\n        return this;\n    }\n\n    public Search withField(String field) {\n        if (!withFieldParam.contains(field)) {\n            withFieldParam.add(field);\n        }\n        return this;\n    }\n\n    public Search sortBy(String field, String dir) {\n        HashMap<String, Object> sortBucket = new HashMap<String, Object>();\n        sortBucket.put(field, dir);\n        for (int i = 0; i < sortByParam.size(); i++) {\n            if (sortByParam.get(i).containsKey(field)){\n                sortByParam.add(i, sortBucket);\n                return this;\n            }\n        }\n        sortByParam.add(sortBucket);\n        return this;\n    }\n\n    public Search fields(String field) {\n        if (!fields.contains(field)) {\n            fields.add(field);\n        }\n        return this;\n    }\n\n    public HashMap<String, Object> toQuery() {\n        HashMap<String, Object> queryParams = new HashMap<String, Object>(this.params);\n        if (withFieldParam.size() > 0) {\n            queryParams.put(\"with_field\", withFieldParam);\n        }\n        if(sortByParam.size() > 0) {\n            queryParams.put(\"sort_by\", sortByParam);\n        }\n        if(aggregateParam.size() > 0) {\n            queryParams.put(\"aggregate\", aggregateParam);\n        }\n        if(fields.size() > 0) {\n            queryParams.put(\"fields\", fields);\n        }\n        return queryParams;\n    }\n\n    public ApiResponse execute() throws Exception {\n        Map<String, String> options = ObjectUtils.asMap(\"content_type\", \"json\");\n        return this.api.callApi(Api.HttpMethod.POST, Arrays.asList(\"resources\", \"search\"), this.toQuery(), options);\n    }\n\n\n    public String toUrl() throws Exception {\n        return toUrl(null, null);\n    }\n\n    public String toUrl(String nextCursor) throws Exception {\n        return toUrl(null, nextCursor);\n    }\n    /***\n     Creates a signed Search URL that can be used on the client side.\n     ***/\n    public String toUrl(Integer ttl, String nextCursor) throws Exception {\n        String nextCursorParam = nextCursor;\n        String apiSecret = api.cloudinary.config.apiSecret;\n        if (apiSecret == null) throw new IllegalArgumentException(\"Must supply api_secret\");\n        if(ttl == null) {\n            ttl = this.ttl;\n        }\n        HashMap queryParams = toQuery();\n        if(nextCursorParam == null) {\n            nextCursorParam = (String) queryParams.get(\"next_cursor\");\n        }\n        queryParams.remove(\"next_cursor\");\n        JSONObject json = ObjectUtils.toJSON(queryParams);\n        String base64Query = Base64Coder.encodeURLSafeString(json.toString());\n        String signature = StringUtils.encodeHexString(Util.hash(String.format(\"%d%s%s\", ttl, base64Query, apiSecret), SignatureAlgorithm.SHA256));\n        String prefix = Url.unsignedDownloadUrlPrefix(null,api.cloudinary.config);\n\n        return String.format(\"%s/search/%s/%d/%s%s\", prefix, signature, ttl, base64Query,nextCursorParam != null && !nextCursorParam.isEmpty() ? \"/\"+nextCursorParam : \"\");\n    }\n}\n"
  },
  {
    "path": "cloudinary-core/src/main/java/com/cloudinary/SearchFolders.java",
    "content": "package com.cloudinary;\n\nimport com.cloudinary.api.ApiResponse;\nimport com.cloudinary.utils.ObjectUtils;\n\nimport java.util.Arrays;\nimport java.util.Map;\n\npublic class SearchFolders extends Search {\n\n    public SearchFolders(Cloudinary cloudinary) {\n        super(cloudinary);\n    }\n\n    public ApiResponse execute() throws Exception {\n        Map<String, String> options = ObjectUtils.asMap(\"content_type\", \"json\");\n        return this.api.callApi(Api.HttpMethod.POST, Arrays.asList(\"folders\", \"search\"), this.toQuery(), options);\n    }\n}\n"
  },
  {
    "path": "cloudinary-core/src/main/java/com/cloudinary/SignatureAlgorithm.java",
    "content": "package com.cloudinary;\n\n/**\n * Defines supported algorithms for generating/verifying hashed message authentication codes (HMAC).\n */\npublic enum SignatureAlgorithm {\n    SHA1(\"SHA-1\"),\n    SHA256(\"SHA-256\");\n\n    private final String algorithmId;\n\n    SignatureAlgorithm(String algorithmId) {\n        this.algorithmId = algorithmId;\n    }\n\n    public String getAlgorithmId() {\n        return algorithmId;\n    }\n}\n"
  },
  {
    "path": "cloudinary-core/src/main/java/com/cloudinary/SmartUrlEncoder.java",
    "content": "package com.cloudinary;\n\nimport java.io.UnsupportedEncodingException;\nimport java.net.URLEncoder;\n\npublic final class SmartUrlEncoder {\n    private SmartUrlEncoder() {}\n\n    public static String encode(String input) {\n        try {\n            return URLEncoder.encode(input, \"UTF-8\").replace(\"%2F\", \"/\").replace(\"%3A\", \":\").replace(\"+\", \"%20\");\n        } catch (UnsupportedEncodingException e) {\n            throw new RuntimeException(e);\n        }\n    }\n}\n"
  },
  {
    "path": "cloudinary-core/src/main/java/com/cloudinary/StoredFile.java",
    "content": "package com.cloudinary;\n\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\npublic class StoredFile {\n    protected Long version;\n\n    protected String publicId;\n\n    protected String format;\n\n    protected String signature;\n\n    protected String type = \"upload\";\n\n    protected String resourceType = \"image\";\n\n    private static final String IMAGE_RESOURCE_TYPE = \"image\";\n\n    private static final String VIDEO_RESOURCE_TYPE = \"video\";\n\n    private static final String AUTO_RESOURCE_TYPE = \"auto\";\n\n    private static final Pattern PRELOADED_PATTERN = Pattern.compile(\"^([^\\\\/]+)\\\\/([^\\\\/]+)\\\\/v(\\\\d+)\\\\/([^#]+)#?([^\\\\/]+)?$\");\n\n    public Long getVersion() {\n        return version;\n    }\n\n    public void setVersion(Long version) {\n        this.version = version;\n    }\n\n    public String getPublicId() {\n        return publicId;\n    }\n\n    public void setPublicId(String publicId) {\n        this.publicId = publicId;\n    }\n\n    protected String getPublicIdForSigning() {\n        return publicId + ((format != null && !format.isEmpty() && resourceType.equals(\"raw\")) ? \".\" + format : \"\");\n    }\n\n    public String getFormat() {\n        return format;\n    }\n\n    public void setFormat(String format) {\n        this.format = format;\n    }\n\n    public String getSignature() {\n        return signature;\n    }\n\n    public void setSignature(String signature) {\n        this.signature = signature;\n    }\n\n    public String getResourceType() {\n        return resourceType;\n    }\n\n    public void setResourceType(String resourceType) {\n        this.resourceType = resourceType;\n    }\n\n    public String getType() {\n        return type;\n    }\n\n    public void setType(String type) {\n        this.type = type;\n    }\n\n    public String getPreloadedFile() {\n        StringBuilder sb = new StringBuilder();\n        sb.append(resourceType).append(\"/\").append(type).append(\"/v\").append(version).append(\"/\").append(publicId);\n        if (format != null && !format.isEmpty()) {\n            sb.append(\".\").append(format);\n        }\n        if (signature != null && !signature.isEmpty()) {\n            sb.append(\"#\").append(signature);\n        }\n        return sb.toString();\n    }\n\n    public void setPreloadedFile(String uri) {\n        if (uri.matches(PRELOADED_PATTERN.pattern())) {\n            Matcher match = PRELOADED_PATTERN.matcher(uri);\n            match.find();\n            resourceType = match.group(1);\n            type = match.group(2);\n            version = Long.parseLong(match.group(3));\n            String filename = match.group(4);\n            if (match.groupCount() == 5)\n                signature = match.group(5);\n            int lastDotIndex = filename.lastIndexOf('.');\n            if (lastDotIndex == -1) {\n                publicId = filename;\n            } else {\n                publicId = filename.substring(0, lastDotIndex);\n                format = filename.substring(lastDotIndex + 1);\n            }\n        }\n    }\n\n    public String getComputedSignature(Cloudinary cloudinary) {\n        Map<String, Object> params = new HashMap<String, Object>();\n        params.put(\"version\", getVersion().toString());\n        params.put(\"public_id\", getPublicIdForSigning());\n        cloudinary.signRequest(params, new HashMap<String, Object>());\n        return params.get(\"signature\").toString();\n    }\n\n    public boolean getIsImage() {\n        return IMAGE_RESOURCE_TYPE.equals(resourceType) || AUTO_RESOURCE_TYPE.equals(resourceType);\n    }\n\n    public boolean getIsVideo() {\n        return VIDEO_RESOURCE_TYPE.equals(resourceType);\n    }\n}\n"
  },
  {
    "path": "cloudinary-core/src/main/java/com/cloudinary/Transformation.java",
    "content": "package com.cloudinary;\n\nimport java.io.Serializable;\nimport java.util.*;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\nimport com.cloudinary.transformation.AbstractLayer;\nimport com.cloudinary.transformation.Condition;\nimport com.cloudinary.transformation.Expression;\nimport com.cloudinary.utils.ObjectUtils;\nimport com.cloudinary.utils.StringUtils;\n\n@SuppressWarnings({\"rawtypes\", \"unchecked\"})\npublic class Transformation<T extends Transformation> implements Serializable {\n    public static final String VAR_NAME_RE = \"^\\\\$[a-zA-Z][a-zA-Z0-9]+$\";\n    protected Map transformation;\n    protected List<Map> transformations;\n    protected String htmlWidth;\n    protected String htmlHeight;\n    protected boolean hiDPI = false;\n    protected boolean isResponsive = false;\n    protected static boolean defaultIsResponsive = false;\n    protected static Object defaultDPR = null;\n\n    private static final Map<String, String> DEFAULT_RESPONSIVE_WIDTH_TRANSFORMATION = ObjectUtils.asMap(\"width\", \"auto\", \"crop\", \"limit\");\n    protected static Map responsiveWidthTransformation = null;\n    private static final Pattern RANGE_VALUE_RE = Pattern.compile(\"^((?:\\\\d+\\\\.)?\\\\d+)([%pP])?$\");\n    private static final Pattern RANGE_RE = Pattern.compile(\"^(\\\\d+\\\\.)?\\\\d+[%pP]?\\\\.\\\\.(\\\\d+\\\\.)?\\\\d+[%pP]?$\");\n    private static final String[] SIMPLE_PARAMS = new String[]{\n            \"ac\", \"audio_codec\",\n            \"af\", \"audio_frequency\",\n            \"bo\", \"border\",\n            \"br\", \"bit_rate\",\n            \"cs\", \"color_space\",\n            \"d\", \"default_image\",\n            \"dl\", \"delay\",\n            \"dn\", \"density\",\n            \"f\", \"fetch_format\",\n            \"fn\", \"custom_function\",\n            \"fps\", \"fps\",\n            \"g\", \"gravity\",\n            \"l\", \"overlay\",\n            \"p\", \"prefix\",\n            \"pg\", \"page\",\n            \"u\", \"underlay\",\n            \"vs\", \"video_sampling\",\n            \"sp\", \"streaming_profile\",\n            \"ki\", \"keyframe_interval\"\n    };\n\n    public Transformation(Transformation transformation) {\n        this(dup(transformation.transformations));\n        this.hiDPI = transformation.isHiDPI();\n        this.isResponsive = transformation.isResponsive();\n    }\n\n    // Warning: options will destructively updated!\n    public Transformation(List<Map> transformations) {\n        this.transformations = transformations;\n        if (transformations.isEmpty()) {\n            chain();\n        } else {\n            this.transformation = transformations.get(transformations.size() - 1);\n        }\n    }\n\n    public Transformation() {\n        this.transformations = new ArrayList<Map>();\n        chain();\n    }\n\n    public T width(Object value) {\n        return param(\"width\", value);\n    }\n\n    public T height(Object value) {\n        return param(\"height\", value);\n    }\n\n    public T named(String... value) {\n        return param(\"transformation\", value);\n    }\n\n    public T crop(String value) {\n        return param(\"crop\", value);\n    }\n\n    public T background(String value) {\n        return param(\"background\", value);\n    }\n\n    public T color(String value) {\n        return param(\"color\", value);\n    }\n\n    public T effect(String value) {\n        return param(\"effect\", value);\n    }\n\n    public T effect(String effect, Object param) {\n        return param(\"effect\", effect + \":\" + param);\n    }\n\n    public T angle(int value) {\n        return param(\"angle\", value);\n    }\n\n    public T angle(String... value) {\n        return param(\"angle\", value);\n    }\n\n    public T border(String value) {\n        return param(\"border\", value);\n    }\n\n    public T border(int width, String color) {\n        return param(\"border\", \"\" + width + \"px_solid_\" + replaceColorPrefix(color));\n    }\n\n    public T x(Object value) {\n        return param(\"x\", value);\n    }\n\n    public T y(Object value) {\n        return param(\"y\", value);\n    }\n\n    /**\n     * Add rounding transformation.\n     * <p>\n     * Radius can be specified either as value in pixels or expression. Specify 0 to keep corner untouched.\n     *\n     * @param value rounding radius for all four corners\n     * @return updated transformation instance for chaining\n     */\n    public T radius(Object value) {\n        return radius(new Object[]{value});\n    }\n\n    /**\n     * Add rounding transformation.\n     * <p>\n     * Radius can be specified either as value in pixels or expression. Specify 0 to keep corner untouched.\n     *\n     * @param topLeftBottomRight rounding radius for top-left and bottom-right corners\n     * @param topRightBottomLeft rounding radius for top-right and bottom-left corners\n     * @return updated transformation instance for chaining\n     */\n    public T radius(Object topLeftBottomRight, Object topRightBottomLeft) {\n        return radius(new Object[]{topLeftBottomRight, topRightBottomLeft});\n    }\n\n    /**\n     * Add rounding transformation.\n     * <p>\n     * Radius can be specified either as value in pixels or expression. Specify 0 to keep corner untouched.\n     *\n     * @param topLeft            rounding radius for top-left corner\n     * @param topRightBottomLeft rounding radius for top-right and bottom-left corners\n     * @param bottomRight        rounding radius for bottom-right corner\n     * @return updated transformation instance for chaining\n     */\n    public T radius(Object topLeft, Object topRightBottomLeft, Object bottomRight) {\n        return radius(new Object[]{topLeft, topRightBottomLeft, bottomRight});\n    }\n\n    /**\n     * Add rounding transformation.\n     * <p>\n     * Radius can be specified either as value in pixels or expression. Specify 0 to keep corner untouched.\n     *\n     * @param topLeft     rounding radius for top-left corner\n     * @param topRight    rounding radius for top-right corner\n     * @param bottomRight rounding radius for bottom-right corner\n     * @param bottomLeft  rounding radius for bottom-left corner\n     * @return updated transformation instance for chaining\n     */\n    public T radius(Object topLeft, Object topRight, Object bottomRight, Object bottomLeft) {\n        return radius(new Object[]{topLeft, topRight, bottomRight, bottomLeft});\n    }\n\n    /**\n     * Add rounding transformation.\n     * <p>\n     * Radius can be specified either as value in pixels or expression. Specify 0 to keep corner untouched.\n     *\n     * @param cornerRadiuses rounding radiuses for corners as array\n     * @return updated transformation instance for chaining\n     */\n    public T radius(Object[] cornerRadiuses) {\n        return param(\"radius\", cornerRadiuses);\n    }\n\n    public T quality(Object value) {\n        return param(\"quality\", value);\n    }\n\n    public T defaultImage(String value) {\n        return param(\"default_image\", value);\n    }\n\n    public T gravity(String value) {\n        return param(\"gravity\", value);\n    }\n\n    /**\n     * Set the keyframe interval parameter\n     *\n     * @param value Interval in seconds\n     * @return The transformation for chaining\n     */\n    public T keyframeInterval(float value) {\n        return param(\"keyframe_interval\", value);\n    }\n\n    /**\n     * Set the keyframe interval parameter\n     *\n     * @param value Interval in seconds.\n     * @return The transformation for chaining\n     */\n    public T keyframeInterval(String value) {\n        return param(\"keyframe_interval\", value);\n    }\n\n    public T colorSpace(String value) {\n        return param(\"color_space\", value);\n    }\n\n    public T prefix(String value) {\n        return param(\"prefix\", value);\n    }\n\n    public T overlay(String value) {\n        return param(\"overlay\", value);\n    }\n\n    public T overlay(AbstractLayer<?> value) {\n        return param(\"overlay\", value);\n    }\n\n    public T underlay(String value) {\n        return param(\"underlay\", value);\n    }\n\n    public T underlay(AbstractLayer<?> value) {\n        return param(\"underlay\", value);\n    }\n\n    public T fetchFormat(String value) {\n        return param(\"fetch_format\", value);\n    }\n\n    public T density(Object value) {\n        return param(\"density\", value);\n    }\n\n    public T page(Object value) {\n        return param(\"page\", value);\n    }\n\n    public T delay(Object value) {\n        return param(\"delay\", value);\n    }\n\n    public T opacity(Object value) {\n        return param(\"opacity\", value);\n    }\n\n    public T rawTransformation(String value) {\n        return param(\"raw_transformation\", value);\n    }\n\n    public T flags(String... value) {\n        return param(\"flags\", value);\n    }\n\n    public T dpr(float value) {\n        return param(\"dpr\", value);\n    }\n\n    public T dpr(int value) {\n        return param(\"dpr\", value);\n    }\n\n    public T dpr(String value) {\n        return param(\"dpr\", value);\n    }\n\n    public T duration(String value) {\n        return param(\"duration\", value);\n    }\n\n    public T duration(float value) {\n        return param(\"duration\", new Float(value));\n    }\n\n    public T duration(double value) {\n        return param(\"duration\", new Double(value));\n    }\n\n    public T durationPercent(float value) {\n        return param(\"duration\", new Float(value).toString() + \"p\");\n    }\n\n    public T durationPercent(double value) {\n        return param(\"duration\", new Double(value).toString() + \"p\");\n    }\n\n    public T startOffset(String value) {\n        return param(\"start_offset\", value);\n    }\n\n    public T startOffset(float value) {\n        return param(\"start_offset\", new Float(value));\n    }\n\n    public T startOffset(double value) {\n        return param(\"start_offset\", new Double(value));\n    }\n\n    public T startOffsetPercent(float value) {\n        return param(\"start_offset\", new Float(value).toString() + \"p\");\n    }\n\n    public T startOffsetPercent(double value) {\n        return param(\"start_offset\", new Double(value).toString() + \"p\");\n    }\n\n    public T endOffset(String value) {\n        return param(\"end_offset\", value);\n    }\n\n    public T endOffset(float value) {\n        return param(\"end_offset\", new Float(value));\n    }\n\n    public T endOffset(double value) {\n        return param(\"end_offset\", new Double(value));\n    }\n\n    public T endOffsetPercent(float value) {\n        return param(\"end_offset\", new Float(value).toString() + \"p\");\n    }\n\n    public T endOffsetPercent(double value) {\n        return param(\"end_offset\", new Double(value).toString() + \"p\");\n    }\n\n    public T offset(String value) {\n        return param(\"offset\", value);\n    }\n\n    public T offset(String[] value) {\n        if (value.length < 2) throw new IllegalArgumentException(\"Offset range must include at least 2 items\");\n        return param(\"offset\", value);\n    }\n\n    public T offset(float[] value) {\n        if (value.length < 2) throw new IllegalArgumentException(\"Offset range must include at least 2 items\");\n        Number[] numberArray = new Number[]{value[0], value[1]};\n        return offset(numberArray);\n    }\n\n    public T offset(double[] value) {\n        if (value.length < 2) throw new IllegalArgumentException(\"Offset range must include at least 2 items\");\n        Number[] numberArray = new Number[]{value[0], value[1]};\n        return offset(numberArray);\n    }\n\n    public T offset(Number[] value) {\n        if (value.length < 2) throw new IllegalArgumentException(\"Offset range must include at least 2 items\");\n        return param(\"offset\", value);\n    }\n\n    public T videoCodec(String value) {\n        return param(\"video_codec\", value);\n    }\n\n    public T videoCodec(Map<String, String> value) {\n        return param(\"video_codec\", value);\n    }\n\n    public T audioCodec(String value) {\n        return param(\"audio_codec\", value);\n    }\n\n    public T audioFrequency(String value) {\n        return param(\"audio_frequency\", value);\n    }\n\n    public T audioFrequency(int value) {\n        return param(\"audio_frequency\", value);\n    }\n\n    public T bitRate(String value) {\n        return param(\"bit_rate\", value);\n    }\n\n    public T bitRate(int value) {\n        return param(\"bit_rate\", new Integer(value));\n    }\n\n    public T videoSampling(String value) {\n        return param(\"video_sampling\", value);\n    }\n\n    public T videoSamplingFrames(int value) {\n        return param(\"video_sampling\", value);\n    }\n\n    public T videoSamplingSeconds(Number value) {\n        return param(\"video_sampling\", value.toString() + \"s\");\n    }\n\n    public T videoSamplingSeconds(int value) {\n        return videoSamplingSeconds(new Integer(value));\n    }\n\n    public T videoSamplingSeconds(float value) {\n        return videoSamplingSeconds(new Float(value));\n    }\n\n    public T videoSamplingSeconds(double value) {\n        return videoSamplingSeconds(new Double(value));\n    }\n\n    public T zoom(String value) {\n        return param(\"zoom\", value);\n    }\n\n    public T zoom(float value) {\n        return param(\"zoom\", new Float(value));\n    }\n\n    public T zoom(double value) {\n        return param(\"zoom\", new Double(value));\n    }\n\n    public T aspectRatio(double value) {\n        return param(\"aspect_ratio\", new Double(value));\n    }\n\n    public T aspectRatio(String value) {\n        return param(\"aspect_ratio\", value);\n    }\n\n    public T aspectRatio(int nom, int denom) {\n        return aspectRatio(Integer.toString(nom) + \":\" + Integer.toString(denom));\n    }\n\n    public T responsiveWidth(boolean value) {\n        return param(\"responsive_width\", value);\n    }\n\n    /**\n     * Start defining a condition, which will be completed with a call {@link Condition#then()}\n     *\n     * @return condition\n     */\n    public Condition ifCondition() {\n        return new Condition().setParent(this);\n    }\n\n    /**\n     * Define a conditional transformation defined by the condition string\n     *\n     * @param condition a condition string\n     * @return the transformation for chaining\n     */\n    public T ifCondition(String condition) {\n        return param(\"if\", condition);\n    }\n\n\n    /**\n     * Define a conditional transformation\n     *\n     * @param expression a condition\n     * @return the transformation for chaining\n     */\n    public T ifCondition(Expression expression) {\n        return ifCondition(expression.toString());\n    }\n\n    /**\n     * Define a conditional transformation\n     *\n     * @param condition a condition\n     * @return the transformation for chaining\n     */\n    public T ifCondition(Condition condition) {\n        return ifCondition(condition.toString());\n    }\n\n    public T ifElse() {\n        chain();\n        return param(\"if\", \"else\");\n    }\n\n    public T endIf() {\n        chain();\n        int transSize = this.transformations.size();\n        for (int i = transSize - 1; i >= 0; i--) {\n            Map segment = this.transformations.get(i); // [..., {if: \"w_gt_1000\",c: \"fill\", w: 500}, ...]\n            Object value = segment.get(\"if\");\n            if (value != null) { // if: \"w_gt_1000\"\n                String ifValue = value.toString();\n                if (ifValue.equals(\"end\")) break;\n                if (segment.size() > 1) {\n                    segment.remove(\"if\"); // {c: fill, w: 500}\n                    transformations.set(i, segment); // [..., {c: fill, w: 500}, ...]\n                    transformations.add(i, ObjectUtils.asMap(\"if\", value)); // // [..., \"if_w_gt_1000\", {c: fill, w: 500}, ...]\n                }\n                if (!\"else\".equals(ifValue)) break; // otherwise keep looking for if_condition\n            }\n        }\n\n        param(\"if\", \"end\");\n        return chain();\n    }\n\n    /**\n     * fps (frames per second) parameter for video\n     *\n     * @param value Either a single value int or float or a range in the format <code>&lt;start&gt;[-&lt;end&gt;]</code>.  <br>\n     *              For example, <code>23-29.7</code>\n     * @return the transformation for chaining\n     */\n    public T fps(String value) {\n        return param(\"fps\", value);\n    }\n\n    /**\n     * fps (frames per second) parameter for video\n     *\n     * @param value the desired fps\n     * @return the transformation for chaining\n     */\n    public T fps(double value) {\n        return param(\"fps\", new Float(value));\n    }\n\n    /**\n     * fps (frames per second) parameter for video\n     *\n     * @param value the desired fps\n     * @return the transformation for chaining\n     */\n    public T fps(int value) {\n        return param(\"fps\", new Integer(value));\n    }\n\n    /**\n     * fps (frames per second) parameter for video\n     * @param rangeStart String or Number, can be null for open range.\n     * @param rangeEnd String or Number, can be null for open range.\n     * @return the transformation for chaining.\n     */\n    public T fps(Object rangeStart, Object rangeEnd){\n        if (rangeEnd == null && rangeStart == null){\n            throw new IllegalArgumentException(\"At least one of [rangeStart, rangeEnd] must be provided\");\n        }\n        StringBuilder builder = new StringBuilder();\n        if (rangeStart != null){\n            builder.append(rangeStart);\n        }\n\n        builder.append(\"-\");\n\n        if (rangeEnd != null){\n            builder.append(rangeEnd);\n        }\n\n        return param(\"fps\", builder.toString());\n    }\n\n    public T streamingProfile(String value) {\n        return param(\"streaming_profile\", value);\n    }\n\n    public boolean isResponsive() {\n        return this.isResponsive;\n    }\n\n    public boolean isHiDPI() {\n        return this.hiDPI;\n    }\n\n    // Warning: options will destructively updated!\n    public T params(Map transformation) {\n        this.transformation = transformation;\n        transformations.add(transformation);\n        return (T) this;\n    }\n\n    public T chain() {\n        return params(new HashMap());\n    }\n\n    public T chainWith(Transformation transformation) {\n        List<Map> transformations = dup(this.transformations);\n        transformations.addAll(dup(transformation.transformations));\n        return (T) new Transformation(transformations);\n    }\n\n    public T param(String key, Object value) {\n        transformation.put(key, value);\n        return (T) this;\n    }\n\n    /**\n     * Serialize this transformation object as a string\n     * <p>\n     * {@code\n     * Transformation().width(100).height(101).generate(); // produces \"h_101,w_100\"\n     * }\n     *\n     * @return a String representation of the transformation\n     */\n    public String generate() {\n        return generate(transformations);\n    }\n\n    @Override\n    public String toString() {\n        return generate();\n    }\n\n    public String generate(Iterable<Map> optionsList) {\n        List<String> components = new ArrayList<String>();\n        for (Map options : optionsList) {\n            if (options.size() > 0) {\n                components.add(generate(options));\n            }\n        }\n        return StringUtils.join(components, \"/\");\n    }\n\n    public String generate(Map options) {\n        boolean isResponsive = ObjectUtils.asBoolean(options.get(\"responsive_width\"), defaultIsResponsive);\n\n        String size = (String) options.get(\"size\");\n        if (size != null) {\n            String[] size_components = size.split(\"x\");\n            options.put(\"width\", size_components[0]);\n            options.put(\"height\", size_components[1]);\n        }\n        String width = this.htmlWidth = ObjectUtils.asString(options.get(\"width\"));\n        String height = this.htmlHeight = ObjectUtils.asString(options.get(\"height\"));\n        boolean hasLayer = options.get(\"overlay\") != null && StringUtils.isNotBlank(options.get(\"overlay\").toString())\n                || options.get(\"underlay\") != null && StringUtils.isNotBlank(options.get(\"underlay\").toString());\n\n        String crop = (String) options.get(\"crop\");\n        String angle = StringUtils.join(ObjectUtils.asArray(options.get(\"angle\")), \".\");\n\n        boolean noHtmlSizes = hasLayer || StringUtils.isNotBlank(angle) || \"fit\".equals(crop) || \"limit\".equals(crop);\n        if (width != null && (width.startsWith(\"auto\") || !isValidAttrValue(width) || noHtmlSizes || isResponsive)) {\n            this.htmlWidth = null;\n        }\n        if (height != null && (!isValidAttrValue(height) || noHtmlSizes || isResponsive)) {\n            this.htmlHeight = null;\n        }\n\n        String background = (String) options.get(\"background\");\n        if (background != null) {\n            background = replaceColorPrefix(background);\n        }\n\n        String color = (String) options.get(\"color\");\n        if (color != null) {\n            color = replaceColorPrefix(color);\n        }\n\n        List transformations = ObjectUtils.asArray(options.get(\"transformation\"));\n        boolean allNamed = true;\n        for ( int i =0; i < transformations.size(); i++ ){\n            Object baseTransformation = transformations.get(i);\n            if (baseTransformation instanceof Map) {\n                allNamed = false;\n                break;\n            } else if (baseTransformation instanceof String){\n                transformations.set(i, ((String) baseTransformation).replaceAll(\" \", \"%20\"));\n            }\n        }\n        String namedTransformation = null;\n        if (allNamed) {\n            namedTransformation = StringUtils.join(transformations, \".\");\n            transformations = new ArrayList();\n        } else {\n            List ts = transformations;\n            transformations = new ArrayList();\n            for (Object baseTransformation : ts) {\n                String transformationString;\n                if (baseTransformation instanceof Map) {\n                    transformationString = generate((Map) baseTransformation);\n                } else {\n                    Map map = new HashMap();\n                    map.put(\"transformation\", baseTransformation);\n                    transformationString = generate(map);\n                }\n                transformations.add(transformationString);\n            }\n        }\n\n\n        String flags = StringUtils.join(ObjectUtils.asArray(options.get(\"flags\")), \".\");\n\n        String duration = normRangeValue(options.get(\"duration\"));\n        String startOffset = normAutoRangeValue(options.get(\"start_offset\"));\n        String endOffset = normRangeValue(options.get(\"end_offset\"));\n        String[] offset = splitRange(options.get(\"offset\"));\n        if (offset != null) {\n            startOffset = normAutoRangeValue(offset[0]);\n            endOffset = normRangeValue(offset[1]);\n        }\n\n        String videoCodec = processVideoCodecParam(options.get(\"video_codec\"));\n        String dpr = ObjectUtils.asString(options.get(\"dpr\"), null == defaultDPR ? null : defaultDPR.toString());\n\n\n        List<String> components = new ArrayList<String>();\n\n        String ifValue = (String) options.get(\"if\");\n        if (ifValue != null) {\n            components.add(0, \"if_\" + Expression.normalize(ifValue));\n        }\n\n        SortedSet<String> varParams = new TreeSet<String>();\n        for (Object k : options.keySet()) {\n            String key = (String) k;\n            if (StringUtils.isVariable(key)) {\n                varParams.add(key + \"_\" + ObjectUtils.asString(options.get(k)));\n            }\n        }\n\n        if (!varParams.isEmpty()) {\n            components.add(StringUtils.join(varParams, \",\"));\n        }\n\n        String variables = processVar((Expression[]) options.get(\"variables\"));\n        if (variables != null) {\n            components.add(variables);\n        }\n\n        Map<String, String> params = new HashMap<String, String>(64);\n\n        params.put(\"a\", Expression.normalize(angle));\n        params.put(\"ar\", Expression.normalize(options.get(\"aspect_ratio\")));\n        params.put(\"b\", background);\n        params.put(\"c\", crop);\n        params.put(\"co\", color);\n        params.put(\"dpr\", Expression.normalize(dpr));\n        params.put(\"du\", duration);\n        params.put(\"e\", Expression.normalize(options.get(\"effect\")));\n        params.put(\"eo\", endOffset);\n        params.put(\"fl\", flags);\n        params.put(\"h\", Expression.normalize(height));\n        params.put(\"o\", Expression.normalize(options.get(\"opacity\")));\n        params.put(\"q\", Expression.normalize(options.get(\"quality\")));\n        params.put(\"r\", Expression.normalize(radiusToExpression((Object[]) options.get(\"radius\"))));\n        params.put(\"so\", startOffset);\n        params.put(\"t\", namedTransformation);\n        params.put(\"vc\", videoCodec);\n        params.put(\"w\", Expression.normalize(width));\n        params.put(\"x\", Expression.normalize(options.get(\"x\")));\n        params.put(\"y\", Expression.normalize(options.get(\"y\")));\n        params.put(\"z\", Expression.normalize(options.get(\"zoom\")));\n\n        for (int i = 0; i < SIMPLE_PARAMS.length; i += 2) {\n            params.put(SIMPLE_PARAMS[i], ObjectUtils.asString(options.get(SIMPLE_PARAMS[i + 1])));\n        }\n\n        params = new TreeMap<String, String>(params);\n\n        for (Map.Entry<String, String> param : params.entrySet()) {\n            if (StringUtils.isNotBlank(param.getValue())) {\n                components.add(param.getKey() + \"_\" + param.getValue());\n            }\n        }\n        String raw_transformation = (String) options.get(\"raw_transformation\");\n        if (raw_transformation != null) {\n            components.add(raw_transformation);\n        }\n        if (!components.isEmpty()) {\n            final String joined = StringUtils.join(components, \",\");\n            transformations.add(joined);\n        }\n\n        if (isResponsive) {\n            transformations.add(generate(getResponsiveWidthTransformation()));\n        }\n\n        if (\"auto\".equals(width) || isResponsive) {\n            this.isResponsive = true;\n        }\n\n        if (\"auto\".equals(dpr)) {\n            this.hiDPI = true;\n        }\n\n        return StringUtils.join(transformations, \"/\");\n    }\n\n    private String replaceColorPrefix(String color) {\n        return StringUtils.replaceIfFirstChar(color, '#', \"rgb:\");\n    }\n\n    private String processVar(Expression[] variables) {\n        if (variables == null) {\n            return null;\n        }\n        List<String> s = new ArrayList<String>(variables.length);\n        for (Expression variable : variables) {\n            s.add(variable.toString());\n        }\n        return StringUtils.join(s, \",\");\n    }\n\n    /**\n     * Check if the value is a float >= 1\n     *\n     * @param value\n     * @return true if the value is a float >= 1\n     */\n    private boolean isValidAttrValue(String value) {\n        final float parseFloat;\n        try {\n            parseFloat = Float.parseFloat(value);\n        } catch (NumberFormatException e) {\n            return false;\n        }\n        return parseFloat >= 1;\n    }\n\n    public String getHtmlWidth() {\n        return htmlWidth;\n    }\n\n    public String getHtmlHeight() {\n        return htmlHeight;\n    }\n\n    private static List<Map> dup(List<Map> transformations) {\n        List<Map> result = new ArrayList<Map>();\n        for (Map params : transformations) {\n            result.add(new HashMap(params));\n        }\n        return result;\n    }\n\n    public static void setResponsiveWidthTransformation(Map transformation) {\n        responsiveWidthTransformation = transformation;\n    }\n\n    private static Map getResponsiveWidthTransformation() {\n        Map result = new HashMap();\n        if (null == responsiveWidthTransformation) {\n            result.putAll(DEFAULT_RESPONSIVE_WIDTH_TRANSFORMATION);\n        } else {\n            result.putAll(responsiveWidthTransformation);\n        }\n        return result;\n    }\n\n    public static void setDefaultIsResponsive(boolean isResponsive) {\n        defaultIsResponsive = isResponsive;\n    }\n\n    public static void setDefaultDPR(Object dpr) {\n        defaultDPR = dpr;\n    }\n\n    private static String[] splitRange(Object range) {\n        if (range instanceof String[] && ((String[]) range).length >= 2) {\n            String[] stringArrayRange = ((String[]) range);\n            return new String[]{stringArrayRange[0], stringArrayRange[1]};\n        } else if (range instanceof Number[] && ((Number[]) range).length >= 2) {\n            Number[] numberArrayRange = ((Number[]) range);\n            return new String[]{numberArrayRange[0].toString(), numberArrayRange[1].toString()};\n        } else if (range instanceof String && RANGE_RE.matcher((String) range).matches()) {\n            return ((String) range).split(\"\\\\.\\\\.\", 2);\n        } else {\n            return null;\n        }\n    }\n\n    private static String normRangeValue(Object objectValue) {\n        if (objectValue == null) return null;\n        String value = objectValue.toString();\n        if (StringUtils.isEmpty(value)) return null;\n\n        Matcher matcher = RANGE_VALUE_RE.matcher(value);\n\n        if (!matcher.matches()) {\n            return Expression.normalize(value);\n        }\n\n        String modifier = \"\";\n        if (matcher.groupCount() == 2 && !StringUtils.isEmpty(matcher.group(2))) {\n            modifier = \"p\";\n        }\n        return matcher.group(1) + modifier;\n    }\n\n    private static String normAutoRangeValue(Object objectValue) {\n        if (\"auto\".equals(objectValue)) {\n            return objectValue.toString();\n        }\n        return normRangeValue(objectValue);\n    }\n\n    private static String processVideoCodecParam(Object param) {\n        StringBuilder outParam = new StringBuilder();\n        if (param instanceof String) {\n            outParam.append(param);\n        }\n        if (param instanceof Map<?, ?>) {\n            Map<String, String> paramMap = (Map<String, String>) param;\n            outParam.append(paramMap.get(\"codec\"));\n            if (paramMap.containsKey(\"profile\")) {\n                outParam.append(\":\").append(paramMap.get(\"profile\"));\n                if (paramMap.containsKey(\"level\")) {\n                    outParam.append(\":\").append(paramMap.get(\"level\"));\n                    if (paramMap.containsKey(\"b_frames\") && paramMap.get(\"b_frames\") == \"false\") {\n                        outParam.append(\":\").append(\"bframes_no\");\n                    }\n                }\n            }\n        }\n        return outParam.toString();\n    }\n\n    /**\n     * Add a variable assignment. Each call to this method will add a new variable assignments, but the order of the assignments may change. To enforce a particular order, use {@link #variables(Expression...)}\n     *\n     * @param name  the name of the variable\n     * @param value the value to assign to the variable\n     * @return this for chaining\n     */\n    public T variable(String name, Object value) {\n        return param(name, value);\n    }\n\n    /**\n     * Add a sequence of variable assignments. The order of the assignments will be honored.\n     *\n     * @param variables variable expressions\n     * @return this for chaining\n     */\n    public T variables(Expression... variables) {\n        return param(\"variables\", variables);\n    }\n\n    /**\n     * Set a custom action, such as a call to a lambda function or a web-assembly function.\n     * @param action The custom action to perform, see {@link CustomFunction}.\n     * @return The transformation for chaining\n     */\n    public T customFunction(CustomFunction action) {\n        return param(\"custom_function\", action.toString());\n    }\n\n    /**\n     * Set a custom pre-function, such as a call to a lambda function or a web-assembly function.\n     * @param action The custom action to perform, see {@link CustomFunction}.\n     * @return The transformation for chaining\n     */\n    public T customPreFunction(CustomFunction action) {\n        return param(\"custom_function\", \"pre:\" + action.toString());\n    }\n\n    private String radiusToExpression(Object[] radiusOption) {\n        if (radiusOption == null) {\n            return null;\n        }\n\n        if (radiusOption.length == 0 || radiusOption.length > 4) {\n            throw new IllegalArgumentException(\"Radius array should contain between 1 and 4 values\");\n        }\n\n        for (Object o : radiusOption) {\n            if (o == null) {\n                throw new IllegalArgumentException(\"Radius options array should not contain nulls\");\n            }\n        }\n\n        return StringUtils.join(radiusOption, \":\");\n    }\n}\n"
  },
  {
    "path": "cloudinary-core/src/main/java/com/cloudinary/Uploader.java",
    "content": "package com.cloudinary;\n\nimport com.cloudinary.strategies.AbstractUploaderStrategy;\nimport com.cloudinary.utils.ObjectUtils;\nimport com.cloudinary.utils.StringUtils;\nimport org.cloudinary.json.JSONObject;\n\nimport java.io.*;\nimport java.util.*;\n\nimport static com.cloudinary.Util.buildGenerateSpriteParams;\nimport static com.cloudinary.Util.buildMultiParams;\n\n@SuppressWarnings({\"rawtypes\", \"unchecked\"})\npublic class Uploader {\n\n    public static final int BUFFER_SIZE = 20000000;\n\n    private final class Command {\n        final static String add = \"add\";\n        final static String remove = \"remove\";\n        final static String replace = \"replace\";\n        final static String removeAll = \"remove_all\";\n\n        private Command() {\n        }\n    }\n\n    public Map callApi(String action, Map<String, Object> params, Map options, Object file) throws IOException {\n        return strategy.callApi(action, params, options, file, null);\n    }\n\n    public Map callApi(String action, Map<String, Object> params, Map options, Object file, ProgressCallback progressCallback) throws IOException {\n        return strategy.callApi(action, params, options, file, progressCallback);\n    }\n\n    private Cloudinary cloudinary;\n    private AbstractUploaderStrategy strategy;\n\n    public Uploader(Cloudinary cloudinary, AbstractUploaderStrategy strategy) {\n        this.cloudinary = cloudinary;\n        this.strategy = strategy;\n        strategy.init(this);\n    }\n\n    public Cloudinary cloudinary() {\n        return this.cloudinary;\n    }\n\n    public Map<String, Object> buildUploadParams(Map options) {\n        return Util.buildUploadParams(options);\n    }\n\n    public Map unsignedUpload(Object file, String uploadPreset, Map options) throws IOException {\n        return unsignedUpload(file, uploadPreset, options, null);\n    }\n\n    public Map unsignedUpload(Object file, String uploadPreset, Map options, ProgressCallback progressCallback) throws IOException {\n        if (options == null)\n            options = ObjectUtils.emptyMap();\n        HashMap nextOptions = new HashMap(options);\n        nextOptions.put(\"unsigned\", true);\n        nextOptions.put(\"upload_preset\", uploadPreset);\n        return upload(file, nextOptions, progressCallback);\n    }\n\n    public Map upload(Object file, Map options) throws IOException {\n        return upload(file, options, null);\n    }\n\n    public Map upload(Object file, Map options, final ProgressCallback progressCallback) throws IOException {\n        if (options == null)\n            options = ObjectUtils.emptyMap();\n        Map<String, Object> params = buildUploadParams(options);\n\n        return callApi(\"upload\", params, options, file, progressCallback);\n    }\n\n    public Map uploadLargeRaw(Object file, Map options) throws IOException {\n        return uploadLargeRaw(file, options, BUFFER_SIZE, null);\n    }\n\n    public Map uploadLargeRaw(Object file, Map options, ProgressCallback progressCallback) throws IOException {\n        return uploadLargeRaw(file, options, BUFFER_SIZE, progressCallback);\n    }\n\n    public Map uploadLargeRaw(Object file, Map options, int bufferSize) throws IOException {\n        return uploadLargeRaw(file, options, bufferSize, null);\n    }\n\n    public Map uploadLargeRaw(Object file, Map options, int bufferSize, ProgressCallback callback) throws IOException {\n        Map sentOptions = new HashMap();\n        sentOptions.putAll(options);\n        sentOptions.put(\"resource_type\", \"raw\");\n        return uploadLarge(file, sentOptions, bufferSize, callback);\n    }\n\n    public Map uploadLarge(Object file, Map options) throws IOException {\n        return uploadLarge(file, options, null);\n    }\n\n    public Map uploadLarge(Object file, Map options, ProgressCallback progressCallback) throws IOException {\n        int bufferSize = ObjectUtils.asInteger(options.get(\"chunk_size\"), BUFFER_SIZE);\n        return uploadLarge(file, options, bufferSize, progressCallback);\n    }\n\n    @SuppressWarnings(\"resource\")\n    public Map uploadLarge(Object file, Map options, int bufferSize) throws IOException {\n        return uploadLarge(file, options, bufferSize, null);\n    }\n\n    public Map uploadLarge(Object file, Map options, int bufferSize, ProgressCallback progressCallback) throws IOException {\n        return uploadLarge(file, options, bufferSize, 0, null, progressCallback);\n    }\n\n    public Map uploadLarge(Object file, Map options, int bufferSize, long offset, String uniqueUploadId, ProgressCallback progressCallback) throws IOException {\n        InputStream input;\n        long length = -1;\n        boolean remote = false;\n        String filename = null;\n        if (file instanceof InputStream) {\n            input = (InputStream) file;\n        } else if (file instanceof File) {\n            length = ((File) file).length();\n            filename = ((File) file).getName();\n            input = new FileInputStream((File) file);\n        } else if (file instanceof byte[]) {\n            length = ((byte[]) file).length;\n            input = new ByteArrayInputStream((byte[]) file);\n        } else {\n            if (StringUtils.isRemoteUrl(file.toString())){\n                remote = true;\n                input = null;\n            } else {\n                File f = new File(file.toString());\n                length = f.length();\n                filename = f.getName();\n                input = new FileInputStream(f);\n            }\n        }\n        try {\n            final Map result;\n            if (remote) {\n                result = upload(file, options);\n            } else {\n                if (!options.containsKey(\"filename\") && StringUtils.isNotBlank(filename)) {\n                    options.put(\"filename\", filename);\n                }\n                result = uploadLargeParts(input, options, bufferSize, length, offset, uniqueUploadId, progressCallback);\n            }\n            return result;\n        } finally {\n            if (input != null) {\n                input.close();\n            }\n        }\n    }\n\n    private Map uploadLargeParts(InputStream input, Map options, int bufferSize, long length, long offset, String uniqueUploadId, final ProgressCallback progressCallback) throws IOException {\n        Map params = buildUploadParams(options);\n\n        Map sentOptions = new HashMap();\n        sentOptions.putAll(options);\n        Map extraHeaders = new HashMap();\n        extraHeaders.put(\"X-Unique-Upload-Id\", StringUtils.isBlank(uniqueUploadId) ? cloudinary().randomPublicId() : uniqueUploadId);\n        sentOptions.put(\"extra_headers\", extraHeaders);\n\n        byte[] buffer = new byte[bufferSize];\n        byte[] nibbleBuffer = new byte[1];\n        int bytesRead = 0;\n        int currentBufferSize = 0;\n        int partNumber = 0;\n        long totalBytes = offset;\n        Map response = null;\n        final long knownLengthBeforeUpload = length;\n        long totalBytesUploaded = offset;\n        input.skip(offset);\n        while (true) {\n            bytesRead = input.read(buffer, currentBufferSize, bufferSize - currentBufferSize);\n            boolean atEnd = bytesRead == -1;\n            boolean fullBuffer = !atEnd && (bytesRead + currentBufferSize) == bufferSize;\n            if (!atEnd) currentBufferSize += bytesRead;\n\n            if (atEnd || fullBuffer) {\n                totalBytes += currentBufferSize;\n                long currentLoc = offset + bufferSize * partNumber;\n                if (!atEnd) {\n                    //verify not on end - try read another byte\n                    bytesRead = input.read(nibbleBuffer, 0, 1);\n                    atEnd = bytesRead == -1;\n                }\n                if (atEnd) {\n                    if (length == -1) length = totalBytes;\n                    byte[] finalBuffer = new byte[currentBufferSize];\n                    System.arraycopy(buffer, 0, finalBuffer, 0, currentBufferSize);\n                    buffer = finalBuffer;\n                }\n                String range = String.format(Locale.US, \"bytes %d-%d/%d\", currentLoc, currentLoc + currentBufferSize - 1, length);\n                extraHeaders.put(\"Content-Range\", range);\n                Map sentParams = new HashMap();\n                sentParams.putAll(params);\n\n                // wrap the callback with another callback to account for multiple parts\n                final long bytesUploadedSoFar = totalBytesUploaded;\n                final ProgressCallback singlePartProgressCallback;\n                if (progressCallback == null) {\n                    singlePartProgressCallback = null;\n                } else {\n                    singlePartProgressCallback = new ProgressCallback() {\n\n                        @Override\n                        public void onProgress(long bytesUploaded, long totalBytes) {\n                            progressCallback.onProgress(bytesUploadedSoFar + bytesUploaded, knownLengthBeforeUpload);\n                        }\n                    };\n                }\n\n                response = callApi(\"upload\", sentParams, sentOptions, buffer, singlePartProgressCallback);\n\n                if (atEnd) break;\n                buffer[0] = nibbleBuffer[0];\n                totalBytesUploaded += currentBufferSize;\n                currentBufferSize = 1;\n                partNumber++;\n            }\n        }\n        return response;\n    }\n\n    public Map destroy(String publicId, Map options) throws IOException {\n        if (options == null)\n            options = ObjectUtils.emptyMap();\n        Map<String, Object> params = new HashMap<String, Object>();\n        params.put(\"type\", (String) options.get(\"type\"));\n        params.put(\"public_id\", publicId);\n        params.put(\"invalidate\", ObjectUtils.asBoolean(options.get(\"invalidate\"), false).toString());\n        params.put(\"notification_url\", (String) options.get(\"notification_url\"));\n        return callApi(\"destroy\", params, options, null);\n    }\n\n    public Map rename(String fromPublicId, String toPublicId, Map options) throws IOException {\n        if (options == null)\n            options = ObjectUtils.emptyMap();\n        Map<String, Object> params = new HashMap<String, Object>();\n        params.put(\"type\", (String) options.get(\"type\"));\n        params.put(\"overwrite\", ObjectUtils.asBoolean(options.get(\"overwrite\"), false).toString());\n        params.put(\"from_public_id\", fromPublicId);\n        params.put(\"to_public_id\", toPublicId);\n        params.put(\"invalidate\", ObjectUtils.asBoolean(options.get(\"invalidate\"), false).toString());\n        params.put(\"to_type\", options.get(\"to_type\"));\n        params.put(\"context\", ObjectUtils.asBoolean(options.get(\"context\"), false).toString());\n        params.put(\"metadata\", ObjectUtils.asBoolean(options.get(\"metadata\"), false).toString());\n        params.put(\"notification_url\", (String) options.get(\"notification_url\"));\n        return callApi(\"rename\", params, options, null);\n    }\n\n    public Map explicit(String publicId, Map options) throws IOException {\n        if (options == null) {\n            options = ObjectUtils.emptyMap();\n        }\n        Map<String, Object> params = buildUploadParams(options);\n        params.put(\"public_id\", publicId);\n        return callApi(\"explicit\", params, options, null);\n    }\n\n    public Map generateSprite(String tag, Map options) throws IOException {\n        if (options == null)\n            options = Collections.singletonMap(\"tag\", tag);\n        else\n            options.put(\"tag\", tag);\n\n        return callApi(\"sprite\", buildGenerateSpriteParams(options), options, null);\n    }\n\n    public Map generateSprite(String[] urls, Map options) throws IOException {\n        if (options == null)\n            options = Collections.singletonMap(\"urls\", urls);\n        else\n            options.put(\"urls\", urls);\n\n        return callApi(\"sprite\", buildGenerateSpriteParams(options), options, null);\n    }\n\n    public Map multi(String[] urls, Map options) throws IOException {\n        if (options == null) {\n            options = Collections.singletonMap(\"urls\", urls);\n        } else {\n            options.put(\"urls\", urls);\n        }\n\n        return multi(options);\n    }\n\n    public Map multi(String tag, Map options) throws IOException {\n        if (options == null) {\n            options = Collections.singletonMap(\"tag\", tag);\n        } else {\n            options.put(\"tag\", tag);\n        }\n\n        return multi(options);\n    }\n\n    private Map multi(Map options) throws IOException {\n        return callApi(\"multi\", buildMultiParams(options), options, null);\n    }\n\n    public Map explode(String public_id, Map options) throws IOException {\n        if (options == null)\n            options = ObjectUtils.emptyMap();\n        Map<String, Object> params = new HashMap<String, Object>();\n        Object transformation = options.get(\"transformation\");\n        if (transformation != null) {\n            if (transformation instanceof Transformation) {\n                transformation = ((Transformation) transformation).generate();\n            }\n            params.put(\"transformation\", transformation.toString());\n        }\n        params.put(\"public_id\", public_id);\n        params.put(\"notification_url\", (String) options.get(\"notification_url\"));\n        params.put(\"format\", (String) options.get(\"format\"));\n        return callApi(\"explode\", params, options, null);\n    }\n\n    /**\n     * Add a tag to one or more assets in your cloud.\n     * Tags are used to categorize and organize your images, and can also be used to apply group actions to images,\n     * for example to delete images, create sprites, ZIP files, JSON lists, or animated GIFs.\n     * Each image can be assigned one or more tags, which is a short name that you can dynamically use (no need to predefine tags).\n     * @param tag - The tag to assign.\n     * @param publicIds - An array of Public IDs of images uploaded to Cloudinary.\n     * @param options - An object holding the available parameters for the request.\n     *                options may include 'exclusive' (boolean) which causes clearing this tag from all other resources\n     * @return A map with the public ids returned from the server\n     * @throws IOException\n     */\n    public Map addTag(String tag, String[] publicIds, Map options) throws IOException {\n        return addTag(new String[]{tag}, publicIds, options);\n    }\n\n    /**\n     * Add a tag to one or more assets in your cloud.\n     * Tags are used to categorize and organize your images, and can also be used to apply group actions to images,\n     * for example to delete images, create sprites, ZIP files, JSON lists, or animated GIFs.\n     * Each image can be assigned one or more tags, which is a short name that you can dynamically use (no need to predefine tags).\n     * @param tag - An array of tags to assign.\n     * @param publicIds - An array of Public IDs of images uploaded to Cloudinary.\n     * @param options - An object holding the available parameters for the request.\n     *                options may include 'exclusive' (boolean) which causes clearing this tag from all other resources\n     * @return A map with the public ids returned from the server.\n     * @throws IOException\n     */\n    public Map addTag(String[] tag, String[] publicIds, Map options) throws IOException {\n        if (options == null)\n            options = ObjectUtils.emptyMap();\n        boolean exclusive = ObjectUtils.asBoolean(options.get(\"exclusive\"), false);\n        String command = exclusive ? \"set_exclusive\" : Command.add;\n        return callTagsApi(tag, command, publicIds, options);\n    }\n\n    /**\n     * Remove a tag to one or more assets in your cloud.\n     * Tags are used to categorize and organize your images, and can also be used to apply group actions to images,\n     * for example to delete images, create sprites, ZIP files, JSON lists, or animated GIFs.\n     * Each image can be assigned one or more tags, which is a short name that you can dynamically use (no need to predefine tags).\n     * @param tag - The tag to remove.\n     * @param publicIds - An array of Public IDs of images uploaded to Cloudinary.\n     * @param options - An object holding the available parameters for the request.\n     *                options may include 'exclusive' (boolean) which causes clearing this tag from all other resources\n     * @return - A map with the public ids returned from the server.\n     * @throws IOException\n     */\n    public Map removeTag(String tag, String[] publicIds, Map options) throws IOException {\n        return removeTag(new String[]{tag}, publicIds, options);\n    }\n\n    /**\n     * Remove tags to one or more assets in your cloud.\n     * Tags are used to categorize and organize your images, and can also be used to apply group actions to images,\n     * for example to delete images, create sprites, ZIP files, JSON lists, or animated GIFs.\n     * Each image can be assigned one or more tags, which is a short name that you can dynamically use (no need to predefine tags).\n     * @param tag - The array of tags to remove.\n     * @param publicIds - An array of Public IDs of images uploaded to Cloudinary.\n     * @param options - An object holding the available parameters for the request.\n     *                options may include 'exclusive' (boolean) which causes clearing this tag from all other resources\n     * @return -      * @return - A map with the public ids returned from the server.\n     * @throws IOException\n     */\n    public Map removeTag(String[] tag, String[] publicIds, Map options) throws IOException {\n        if (options == null)\n            options = ObjectUtils.emptyMap();\n        return callTagsApi(tag, Command.remove, publicIds, options);\n    }\n\n    /**\n     * Remove an array of tags to one or more assets in your cloud.\n     * Tags are used to categorize and organize your images, and can also be used to apply group actions to images,\n     * for example to delete images, create sprites, ZIP files, JSON lists, or animated GIFs.\n     * Each image can be assigned one or more tags, which is a short name that you can dynamically use (no need to predefine tags).\n     * @param publicIds - An array of Public IDs of images uploaded to Cloudinary.\n     * @param options - An object holding the available parameters for the request.\n     *                options may include 'exclusive' (boolean) which causes clearing this tag from all other resources\n     * @return -      * @return - A map with the public ids returned from the server.\n     * @throws IOException\n     */\n    public Map removeAllTags(String[] publicIds, Map options) throws IOException {\n        if (options == null)\n            options = ObjectUtils.emptyMap();\n        return callTagsApi(null, Command.removeAll, publicIds, options);\n    }\n\n    /**\n     * Replaces a tag to one or more assets in your cloud.\n     * Tags are used to categorize and organize your images, and can also be used to apply group actions to images,\n     * for example to delete images, create sprites, ZIP files, JSON lists, or animated GIFs.\n     * Each image can be assigned one or more tags, which is a short name that you can dynamically use (no need to predefine tags).\n     * @param tag - The tag to replace.\n     * @param publicIds - An array of Public IDs of images uploaded to Cloudinary.\n     * @param options - An object holding the available options for the request.\n     *                options may include 'exclusive' (boolean) which causes clearing this tag from all other resources\n     * @return - A map with the public ids returned from the server.\n     * @throws IOException\n     */\n    public Map replaceTag(String tag, String[] publicIds, Map options) throws IOException {\n        return replaceTag(new String[]{tag}, publicIds, options);\n    }\n\n    /**\n     * Replaces tags to one or more assets in your cloud.\n     * Tags are used to categorize and organize your images, and can also be used to apply group actions to images,\n     * for example to delete images, create sprites, ZIP files, JSON lists, or animated GIFs.\n     * Each image can be assigned one or more tags, which is a short name that you can dynamically use (no need to predefine tags).\n     * @param tag - An array of tag to replace.\n     * @param publicIds - An array of Public IDs of images uploaded to Cloudinary.\n     * @param options - An object holding the available options for the request.\n     *                options may include 'exclusive' (boolean) which causes clearing this tag from all other resources\n     * @return - A map with the public ids returned from the server.\n     * @throws IOException\n     */\n    public Map replaceTag(String[] tag, String[] publicIds, Map options) throws IOException {\n        if (options == null)\n            options = ObjectUtils.emptyMap();\n        return callTagsApi(tag, Command.replace, publicIds, options);\n    }\n\n    public Map callTagsApi(String[] tag, String command, String[] publicIds, Map options) throws IOException {\n        if (options == null)\n            options = ObjectUtils.emptyMap();\n        Map<String, Object> params = new HashMap<String, Object>();\n        if (tag != null) {\n            params.put(\"tag\", StringUtils.join(tag, \",\"));\n        }\n        params.put(\"command\", command);\n        params.put(\"type\", (String) options.get(\"type\"));\n        params.put(\"public_ids\", Arrays.asList(publicIds));\n        return callApi(\"tags\", params, options, null);\n    }\n\n    /**\n     * Add a context keys and values. If a particular key already exists, the value associated with the key is updated.\n     * @param context a map of key and value. Serialized to \"key1=value1|key2=value2\"\n     * @param publicIds the public IDs of the resources to update\n     * @param options additional options passed to the request\n     * @return a list of public IDs that were updated\n     * @throws IOException\n     */\n    public Map addContext(Map context, String[] publicIds, Map options) throws IOException {\n        return callContextApi(context, Command.add, publicIds, options);\n    }\n\n    /**\n     * Add a context keys and values. If a particular key already exists, the value associated with the key is updated.\n     * @param context Serialized context in the form of \"key1=value1|key2=value2\"\n     * @param publicIds the public IDs of the resources to update\n     * @param options additional options passed to the request\n     * @return a list of public IDs that were updated\n     * @throws IOException\n     */\n    public Map addContext(String context, String[] publicIds, Map options) throws IOException {\n        return callContextApi(context, Command.add, publicIds, options);\n    }\n\n    /**\n     * Remove all custom context from the specified public IDs.\n     * @param publicIds the public IDs of the resources to update\n     * @param options additional options passed to the request\n     * @return a list of public IDs that were updated\n     * @throws IOException\n     */\n    public Map removeAllContext(String[] publicIds, Map options) throws IOException {\n        return callContextApi((String)null, Command.removeAll, publicIds, options);\n    }\n\n    protected Map callContextApi(Map context, String command, String[] publicIds, Map options) throws IOException {\n        return callContextApi(Util.encodeContext(context), command, publicIds, options);\n    }\n\n    protected Map callContextApi(String context, String command, String[] publicIds, Map options) throws IOException {\n        if (options == null)\n            options = ObjectUtils.emptyMap();\n        Map<String, Object> params = new HashMap<String, Object>();\n        if (context != null) {\n            params.put(\"context\", context);\n        }\n        params.put(\"command\", command);\n        params.put(\"type\", (String) options.get(\"type\"));\n        params.put(\"public_ids\", Arrays.asList(publicIds));\n        return callApi(\"context\", params, options, null);\n    }\n\n    private final static String[] TEXT_PARAMS = {\"public_id\", \"font_family\", \"font_size\", \"font_color\", \"text_align\", \"font_weight\", \"font_style\",\n            \"background\", \"opacity\", \"text_decoration\"};\n\n    public Map text(String text, Map options) throws IOException {\n        if (options == null)\n            options = ObjectUtils.emptyMap();\n        Map<String, Object> params = new HashMap<String, Object>();\n        params.put(\"text\", text);\n        for (String param : TEXT_PARAMS) {\n            params.put(param, ObjectUtils.asString(options.get(param)));\n        }\n        return callApi(\"text\", params, options, null);\n    }\n\n    public Map createArchive(Map options, String targetFormat) throws IOException {\n        Map params = Util.buildArchiveParams(options, targetFormat);\n        return callApi(\"generate_archive\", params, options, null);\n    }\n\n    public Map createZip(Map options) throws IOException {\n        return createArchive(options, \"zip\");\n    }\n\n    public Map createArchive(ArchiveParams params) throws IOException {\n        return createArchive(params.toMap(), params.targetFormat());\n    }\n\n    public void signRequestParams(Map<String, Object> params, Map options) {\n        if (!params.containsKey(\"timestamp\"))\n            params.put(\"timestamp\", Util.timestamp());\n        cloudinary.signRequest(params, options);\n    }\n\n    public String uploadTagParams(Map options) {\n        if (options == null)\n            options = new HashMap();\n        if (options.get(\"resource_type\") == null) {\n            options = new HashMap(options);\n            options.put(\"resource_type\", \"auto\");\n        }\n\n        String callback = ObjectUtils.asString(options.get(\"callback\"), this.cloudinary.config.callback);\n        if (callback == null) {\n            throw new IllegalArgumentException(\"Must supply callback\");\n        }\n        options.put(\"callback\", callback);\n\n        Map<String, Object> params = this.buildUploadParams(options);\n        if (options.get(\"unsigned\") == null || Boolean.FALSE.equals(options.get(\"unsigned\"))) {\n            signRequestParams(params, options);\n        } else {\n            Util.clearEmpty(params);\n        }\n\n        return JSONObject.valueToString(params);\n    }\n\n    public String getUploadUrl(Map options) {\n        if (options == null)\n            options = new HashMap();\n        return this.cloudinary.cloudinaryApiUrl(\"upload\", options);\n    }\n\n    public String unsignedImageUploadTag(String field, String uploadPreset, Map options, Map<String, Object> htmlOptions) {\n        Map nextOptions = new HashMap(options);\n        nextOptions.put(\"upload_preset\", uploadPreset);\n        nextOptions.put(\"unsigned\", true);\n        return imageUploadTag(field, nextOptions, htmlOptions);\n    }\n\n    public String imageUploadTag(String field, Map options, Map<String, Object> htmlOptions) {\n        if (htmlOptions == null)\n            htmlOptions = ObjectUtils.emptyMap();\n\n        String tagParams = StringUtils.escapeHtml(uploadTagParams(options));\n\n        String cloudinaryUploadUrl = getUploadUrl(options);\n\n        StringBuilder builder = new StringBuilder();\n        builder.append(\"<input type='file' name='file' data-url='\").append(cloudinaryUploadUrl).append(\"' data-form-data='\").append(tagParams)\n                .append(\"' data-cloudinary-field='\").append(field).append(\"'\");\n        if (options.containsKey(\"chunk_size\"))\n            builder.append(\" data-max-chunk-size='\").append(options.get(\"chunk_size\")).append(\"'\");\n        builder.append(\" class='cloudinary-fileupload\");\n\n        if (htmlOptions.containsKey(\"class\")) {\n            builder.append(\" \").append(htmlOptions.get(\"class\"));\n        }\n        for (Map.Entry<String, Object> htmlOption : htmlOptions.entrySet()) {\n            if (htmlOption.getKey().equals(\"class\"))\n                continue;\n            builder.append(\"' \").append(htmlOption.getKey()).append(\"='\").append(StringUtils.escapeHtml(ObjectUtils.asString(htmlOption.getValue())));\n        }\n        builder.append(\"'/>\");\n        return builder.toString();\n    }\n\n    public Map deleteByToken(String token) throws Exception {\n        return callApi(\"delete_by_token\", ObjectUtils.asMap(\"token\", token), ObjectUtils.emptyMap(), null);\n    }\n\n    /**\n     * Populates metadata fields with the given values. Existing values will be overwritten.\n     * @param metadata a map of field name and value.\n     * @param publicIds the public IDs of the resources to update\n     * @param options additional options passed to the request\n     * @return a list of public IDs that were updated\n     * @throws IOException\n     */\n    public Map updateMetadata(Map metadata, String[] publicIds, Map options) throws IOException {\n        if (options == null)\n            options = new HashMap();\n\n        Map<String, Object> params = new HashMap<String, Object>();\n        params.put(\"metadata\", Util.encodeContext(metadata));\n        params.put(\"public_ids\", Arrays.asList(publicIds));\n        params.put(\"type\", (String)options.get(\"type\"));\n\n        return callApi(\"metadata\", params, options, null);\n    }\n}\n"
  },
  {
    "path": "cloudinary-core/src/main/java/com/cloudinary/Url.java",
    "content": "package com.cloudinary;\n\nimport java.io.UnsupportedEncodingException;\nimport java.net.MalformedURLException;\nimport java.net.URL;\nimport java.net.URLDecoder;\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.TreeMap;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\nimport java.util.zip.CRC32;\n\nimport com.cloudinary.utils.Base64Coder;\nimport com.cloudinary.utils.ObjectUtils;\nimport com.cloudinary.utils.StringUtils;\n\nimport static com.cloudinary.SignatureAlgorithm.SHA256;\n\npublic class Url {\n    private final Cloudinary cloudinary;\n    private final Configuration config;\n    private boolean longUrlSignature;\n    String publicId = null;\n    String type = null;\n    String resourceType = null;\n    String format = null;\n    String version = null;\n    Transformation transformation = null;\n    boolean signUrl;\n    private AuthToken authToken;\n    String source = null;\n    private String urlSuffix;\n    private Boolean useRootPath;\n    private Boolean useFetchFormat;\n    Map<String, Transformation> sourceTransformation = null;\n    String[] sourceTypes = null;\n    String fallbackContent = null;\n    Transformation posterTransformation = null;\n    String posterSource = null;\n    Url posterUrl = null;\n\n    private static final String CL_BLANK = \"data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7\";\n    public static final String[] DEFAULT_VIDEO_SOURCE_TYPES = {\"webm\", \"mp4\", \"ogv\"};\n    private static final Pattern VIDEO_EXTENSION_RE = Pattern.compile(\"\\\\.(\" + StringUtils.join(DEFAULT_VIDEO_SOURCE_TYPES, \"|\") + \")$\");\n\n    public Url(Cloudinary cloudinary) {\n        this.cloudinary = cloudinary;\n        this.config = new Configuration(cloudinary.config);\n        this.longUrlSignature = config.longUrlSignature;\n        this.authToken = config.authToken;\n    }\n\n    public Url clone() {\n        Url cloned = cloudinary.url();\n        cloned.config.update(config.asMap());\n        cloned.fallbackContent = this.fallbackContent;\n        cloned.format = this.format;\n        cloned.posterSource = this.posterSource;\n        if (this.posterTransformation != null)\n            cloned.posterTransformation = new Transformation(this.posterTransformation);\n        if (this.posterUrl != null) cloned.posterUrl = this.posterUrl.clone();\n        cloned.publicId = this.publicId;\n        cloned.resourceType = this.resourceType;\n        cloned.signUrl = this.signUrl;\n        cloned.source = this.source;\n        if (this.transformation != null) cloned.transformation = new Transformation(this.transformation);\n        if (this.sourceTransformation != null) {\n            cloned.sourceTransformation = new HashMap<String, Transformation>();\n            for (Map.Entry<String, Transformation> keyValuePair : this.sourceTransformation.entrySet()) {\n                cloned.sourceTransformation.put(keyValuePair.getKey(), keyValuePair.getValue());\n            }\n        }\n        cloned.sourceTypes = this.sourceTypes;\n        cloned.urlSuffix = this.urlSuffix;\n        cloned.useRootPath = this.useRootPath;\n        cloned.useFetchFormat = this.useFetchFormat;\n        cloned.longUrlSignature = this.longUrlSignature;\n        cloned.authToken = this.authToken;\n        return cloned;\n    }\n\n    private static Pattern identifierPattern = Pattern.compile(\"^(?:([^/]+)/)??(?:([^/]+)/)??(?:v(\\\\d+)/)?\" + \"(?:([^#/]+?)(?:\\\\.([^.#/]+))?)(?:#([^/]+))?$\");\n\n    /**\n     * Parses a cloudinary identifier of the form:<br>\n     * {@code [<resource_type>/][<image_type>/][v<version>/]<public_id>[.<format>][#<signature>]}\n     */\n    public Url fromIdentifier(String identifier) {\n        Matcher matcher = identifierPattern.matcher(identifier);\n        if (!matcher.matches()) {\n            throw new RuntimeException(String.format(\"Couldn't parse identifier %s\", identifier));\n        }\n\n        String resourceType = matcher.group(1);\n        if (resourceType != null) {\n            resourceType(resourceType);\n        }\n\n        String type = matcher.group(2);\n        if (type != null) {\n            type(type);\n        }\n\n        String version = matcher.group(3);\n        if (version != null) {\n            version(version);\n        }\n\n        String publicId = matcher.group(4);\n        if (publicId != null) {\n            publicId(publicId);\n        }\n\n        String format = matcher.group(5);\n        if (format != null) {\n            format(format);\n        }\n\n        // Signature (group 6) is not used\n\n        return this;\n    }\n\n    public Url type(String type) {\n        this.type = type;\n        return this;\n    }\n\n    public Url resourcType(String resourceType) {\n        return resourceType(resourceType);\n    }\n\n    public Url resourceType(String resourceType) {\n        this.resourceType = resourceType;\n        return this;\n    }\n\n    public Url publicId(Object publicId) {\n        this.publicId = ObjectUtils.asString(publicId);\n        return this;\n    }\n\n    public Url format(String format) {\n        this.format = format;\n        return this;\n    }\n\n    public Url cloudName(String cloudName) {\n        this.config.cloudName = cloudName;\n        return this;\n    }\n\n    public Url secureDistribution(String secureDistribution) {\n        this.config.secureDistribution = secureDistribution;\n        return this;\n    }\n\n    public Url secureCdnSubdomain(boolean secureCdnSubdomain) {\n        this.config.secureCdnSubdomain = secureCdnSubdomain;\n        return this;\n    }\n\n    public Url suffix(String urlSuffix) {\n        this.urlSuffix = urlSuffix;\n        return this;\n    }\n\n    public Url useRootPath(boolean useRootPath) {\n        this.useRootPath = useRootPath;\n        return this;\n    }\n\n    public Url useFetchFormat(boolean useFetchFormat) {\n        this.useFetchFormat = useFetchFormat;\n        return this;\n    }\n\n    public Url cname(String cname) {\n        this.config.cname = cname;\n        return this;\n    }\n\n    public Url version(Object version) {\n        this.version = ObjectUtils.asString(version);\n        return this;\n    }\n\n    public Url transformation(Transformation transformation) {\n        this.transformation = transformation;\n        return this;\n    }\n\n    public Url secure(boolean secure) {\n        this.config.secure = secure;\n        return this;\n    }\n\n    public Url privateCdn(boolean privateCdn) {\n        this.config.privateCdn = privateCdn;\n        return this;\n    }\n\n    public Url cdnSubdomain(boolean cdnSubdomain) {\n        this.config.cdnSubdomain = cdnSubdomain;\n        return this;\n    }\n\n    public Url shorten(boolean shorten) {\n        this.config.shorten = shorten;\n        return this;\n    }\n\n    public Transformation transformation() {\n        if (this.transformation == null)\n            this.transformation = new Transformation();\n        return this.transformation;\n    }\n\n    public Url signed(boolean signUrl) {\n        this.signUrl = signUrl;\n        return this;\n    }\n\n    /**\n     * Set the authorization token. If <code>authToken</code> has already been set the parameter is <b>merged</b> with the current value unless the parameter value is <code>null</code> or <code>NULL_AUTH_TOKEN</code>.<br><br>\n     * For example, to generate an authorized URL with a different duration:<br>\n     * <pre class=\"prettyprint\">\n     *  {@code\n     *   cloudinary.config.authToken = new AuthToken(KEY).duration(500);\n     *   // later...\n     *   cloudinary.url().signed(true).authToken(new AuthToken().duration(300))\n     *                   .type(\"authenticated\").version(\"1486020273\").generate(\"sample.jpg\");\n     *  }\n     * </pre>\n     *\n     * @param authToken an authorization token object\n     * @return this\n     */\n    public Url authToken(AuthToken authToken) {\n        if (this.authToken == null) {\n            this.authToken = authToken;\n        } else if (authToken == null || authToken.equals(AuthToken.NULL_AUTH_TOKEN)) {\n            this.authToken = authToken;\n        } else {\n            this.authToken = this.authToken.merge(authToken);\n        }\n        return this;\n    }\n\n    public Url longUrlSignature(boolean isLong) {\n        this.longUrlSignature = isLong;\n        return this;\n    }\n\n    public Url sourceTransformation(Map<String, Transformation> sourceTransformation) {\n        this.sourceTransformation = sourceTransformation;\n        return this;\n    }\n\n    public Url sourceTransformationFor(String source, Transformation transformation) {\n        if (this.sourceTransformation == null) {\n            this.sourceTransformation = new HashMap<String, Transformation>();\n        }\n        this.sourceTransformation.put(source, transformation);\n        return this;\n    }\n\n    public Url sourceTypes(String[] sourceTypes) {\n        this.sourceTypes = sourceTypes;\n        return this;\n    }\n\n    public Url fallbackContent(String fallbackContent) {\n        this.fallbackContent = fallbackContent;\n        return this;\n    }\n\n    public Url posterTransformation(Transformation posterTransformation) {\n        this.posterTransformation = posterTransformation;\n        return this;\n    }\n\n    @SuppressWarnings(\"rawtypes\")\n    public Url posterTransformation(List<Map> posterTransformations) {\n        this.posterTransformation = new Transformation(posterTransformations);\n        return this;\n    }\n\n    @SuppressWarnings({\"rawtypes\", \"unchecked\"})\n    public Url posterTransformation(Map posterTransformations) {\n        List<Map> transformations = new ArrayList<Map>();\n        Map copy = new HashMap();\n        copy.putAll(posterTransformations);\n        transformations.add(copy);\n        this.posterTransformation = new Transformation(transformations);\n        return this;\n    }\n\n    public Url posterSource(String posterSource) {\n        this.posterSource = posterSource;\n        return this;\n    }\n\n    public Url posterUrl(Url posterUrl) {\n        this.posterUrl = posterUrl;\n        return this;\n    }\n\n    public Url poster(Object poster) {\n        if (poster instanceof Transformation) {\n            return posterTransformation((Transformation) poster);\n        } else if (poster instanceof List) {\n            return posterTransformation((List) poster);\n        } else if (poster instanceof Map) {\n            return posterTransformation((Map) poster);\n        } else if (poster instanceof Url) {\n            return posterUrl((Url) poster);\n        } else if (poster instanceof String) {\n            return posterSource((String) poster);\n        } else if (poster == null || poster.equals(Boolean.FALSE)) {\n            return posterSource(\"\");\n        } else {\n            throw new IllegalArgumentException(\"Illegal value type supplied to poster. must be one of: <Transformation>, <List<Map>>, <Map>, <Url>, <String>\");\n        }\n    }\n\n    /**\n     *  Indicates whether to add '/v1/' to the URL when the public ID includes folders and a 'version' value was\n     *  not defined.\n     *  When no version is explicitly specified and the public id contains folders, a default v1 version\n     *  is added to the url. This boolean can disable that behaviour.\n     * @param forceVersion  Whether to add the version to the url.\n     * @return This same Url instance for chaining.\n     */\n    public Url forceVersion(boolean forceVersion){\n        this.config.forceVersion = forceVersion;\n        return this;\n    }\n\n    public String generate() {\n        return generate(null);\n    }\n\n    public String generate(String source) {\n        boolean useRootPath = this.config.useRootPath;\n        if (this.useRootPath != null) {\n            useRootPath = this.useRootPath;\n        }\n\n        if (StringUtils.isEmpty(this.config.cloudName)) {\n            throw new IllegalArgumentException(\"Must supply cloud_name in tag or in configuration\");\n        }\n\n        if (source == null) {\n            if (publicId == null) {\n                if (this.source == null) {\n                    return null;\n                }\n                source = this.source;\n            } else {\n                source = publicId;\n            }\n        }\n\n        boolean httpSource = StringUtils.isHttpUrl(source);\n        if (httpSource) {\n            if (StringUtils.isEmpty(type) || \"asset\".equals(type)) {\n                return source;\n            }\n        }\n\n        if ((type != null && type.equals(\"fetch\") || (useFetchFormat != null && useFetchFormat)) && !StringUtils.isEmpty(format)) {\n            transformation().fetchFormat(format);\n            this.format = null;\n        }\n\n        String transformationStr = transformation().generate();\n        String signature = \"\";\n\n        String[] finalizedSource = finalizeSource(source, format, urlSuffix);\n        source = finalizedSource[0];\n        String sourceToSign = finalizedSource[1];\n\n        if (this.config.forceVersion && sourceToSign.contains(\"/\") && !StringUtils.startWithVersionString(sourceToSign) &&\n                !httpSource && StringUtils.isEmpty(version)) {\n            version = \"1\";\n        }\n\n        if (version == null)\n            version = \"\";\n        else\n            version = \"v\" + version;\n\n\n        if (signUrl && (authToken == null || authToken.equals(AuthToken.NULL_AUTH_TOKEN))) {\n            SignatureAlgorithm signatureAlgorithm = longUrlSignature ? SHA256 : config.signatureAlgorithm;\n\n            String toSign = StringUtils.join(new String[]{transformationStr, sourceToSign}, \"/\");\n            toSign = StringUtils.removeStartingChars(toSign, '/');\n            toSign = StringUtils.mergeSlashesInUrl(toSign);\n\n            byte[] hash = Util.hash(toSign + this.config.apiSecret, signatureAlgorithm);\n            signature = Base64Coder.encodeURLSafeString(hash);\n            signature = \"s--\" + signature.substring(0, longUrlSignature ? 32 : 8) + \"--\";\n        }\n\n        String resourceType = this.resourceType;\n        if (resourceType == null) resourceType = \"image\";\n        String finalResourceType = finalizeResourceType(resourceType, type, urlSuffix, useRootPath, config.shorten);\n        String prefix = unsignedDownloadUrlPrefix(source, config);\n        String join = StringUtils.join(new String[]{prefix, finalResourceType, signature, transformationStr, version, source}, \"/\");\n        String url = StringUtils.mergeSlashesInUrl(join);\n\n        if (signUrl && authToken != null && !authToken.equals(AuthToken.NULL_AUTH_TOKEN)) {\n            try {\n                URL tempUrl = new URL(url);\n                String path = tempUrl.getPath();\n                String token = authToken.generate(path);\n                url = url + \"?\" + token;\n            } catch (MalformedURLException ignored) {\n            }\n        }\n        if (cloudinary.config.analytics != null && cloudinary.config.analytics) {\n            try {\n                URL tempUrl = new URL(url);\n                // if any other query param already exist on the URL do not add analytics query param.\n                if (tempUrl.getQuery() == null) {\n                    String path = tempUrl.getPath();\n                    url = url + \"?\" + cloudinary.analytics.toQueryParam();\n                }\n            } catch (MalformedURLException ignored) {\n            }\n        }\n        return url;\n    }\n\n    private String[] finalizeSource(String source, String format, String urlSuffix) {\n        source = StringUtils.mergeSlashesInUrl(source);\n        String[] result = new String[2];\n        String sourceToSign;\n        if (StringUtils.isHttpUrl(source)) {\n            source = SmartUrlEncoder.encode(source);\n            sourceToSign = source;\n        } else {\n            try {\n                source = SmartUrlEncoder.encode(URLDecoder.decode(source.replace(\"+\", \"%2B\"), \"UTF-8\"));\n            } catch (UnsupportedEncodingException e) {\n                throw new RuntimeException(e);\n            }\n            sourceToSign = source;\n            if (StringUtils.isNotBlank(urlSuffix)) {\n                if (urlSuffix.contains(\".\") || urlSuffix.contains(\"/\")) {\n                    throw new IllegalArgumentException(\"url_suffix should not include . or /\");\n                }\n                source = source + \"/\" + urlSuffix;\n            }\n            if (StringUtils.isNotBlank(format)) {\n                source = source + \".\" + format;\n                sourceToSign = sourceToSign + \".\" + format;\n            }\n        }\n        result[0] = source;\n        result[1] = sourceToSign;\n        return result;\n    }\n\n    public String finalizeResourceType(String resourceType, String type, String urlSuffix, boolean useRootPath, boolean shorten) {\n        if (type == null) {\n            type = \"upload\";\n        }\n\n        if (!StringUtils.isBlank(urlSuffix)) {\n            if (resourceType.equals(\"image\") && type.equals(\"upload\")) {\n                resourceType = \"images\";\n                type = null;\n            } else if (resourceType.equals(\"image\") && type.equals(\"private\")) {\n                resourceType = \"private_images\";\n                type = null;\n            } else if (resourceType.equals(\"image\") && type.equals(\"authenticated\")) {\n                resourceType = \"authenticated_images\";\n                type = null;\n            } else if (resourceType.equals(\"raw\") && type.equals(\"upload\")) {\n                resourceType = \"files\";\n                type = null;\n            } else if (resourceType.equals(\"video\") && type.equals(\"upload\")) {\n                resourceType = \"videos\";\n                type = null;\n            } else {\n                throw new IllegalArgumentException(\"URL Suffix only supported for image/upload, image/private, raw/upload, image/authenticated  and video/upload\");\n            }\n        }\n        if (useRootPath) {\n            if ((resourceType.equals(\"image\") && type.equals(\"upload\")) || (resourceType.equals(\"images\") && StringUtils.isBlank(type))) {\n                resourceType = null;\n                type = null;\n            } else {\n                throw new IllegalArgumentException(\"Root path only supported for image/upload\");\n            }\n        }\n        if (shorten && resourceType.equals(\"image\") && type.equals(\"upload\")) {\n            resourceType = \"iu\";\n            type = null;\n        }\n        String result = resourceType;\n        if (type != null) {\n            result += \"/\" + type;\n        }\n        return result;\n    }\n\n    public static String unsignedDownloadUrlPrefix(String source, Configuration config) {\n        if (config.cloudName.startsWith(\"/\")) {\n            return \"/res\" + config.cloudName;\n        }\n        boolean sharedDomain = !config.privateCdn;\n\n        String prefix;\n        String cloudName;\n        String secureDistribution = config.secureDistribution;\n        Boolean secureCdnSubdomain = null;\n\n        if (config.secure) {\n            if (StringUtils.isEmpty(config.secureDistribution) || config.secureDistribution.equals(Cloudinary.OLD_AKAMAI_SHARED_CDN)) {\n                secureDistribution = config.privateCdn ? config.cloudName + \"-res.cloudinary.com\" : Cloudinary.SHARED_CDN;\n            }\n            if (!sharedDomain) {\n                sharedDomain = secureDistribution.equals(Cloudinary.SHARED_CDN);\n            }\n\n            if (secureCdnSubdomain == null && sharedDomain) {\n                secureCdnSubdomain = config.cdnSubdomain;\n            }\n\n            if (secureCdnSubdomain != null && secureCdnSubdomain == true) {\n                secureDistribution = config.secureDistribution.replace(\"res.cloudinary.com\", \"res-\" + shard(source) + \".cloudinary.com\");\n            }\n\n            prefix = \"https://\" + secureDistribution;\n        } else if (StringUtils.isNotBlank(config.cname)) {\n            String subdomain = config.cdnSubdomain ? \"a\" + shard(source) + \".\" : \"\";\n            prefix = \"http://\" + subdomain + config.cname;\n        } else {\n            String protocol = \"http://\";\n            cloudName = config.privateCdn ? config.cloudName + \"-\" : \"\";\n            String res = \"res\";\n            String subdomain = config.cdnSubdomain ? \"-\" + shard(source) : \"\";\n            String domain = \".cloudinary.com\";\n            prefix = StringUtils.join(new String[]{protocol, cloudName, res, subdomain, domain}, \"\");\n        }\n        if (sharedDomain) {\n            prefix += \"/\" + config.cloudName;\n        }\n        return prefix;\n    }\n\n    private static String shard(String input) {\n        CRC32 crc32 = new CRC32();\n        crc32.update(Util.getUTF8Bytes(input));\n        return String.valueOf((crc32.getValue() % 5 + 5) % 5 + 1);\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    public String imageTag(String source) {\n        return imageTag(source, ObjectUtils.emptyMap());\n    }\n\n    public String imageTag(Map<String, String> attributes) {\n        return imageTag(null, attributes);\n    }\n\n    public String imageTag(String source, Map<String, String> attributes) {\n        String url = generate(source);\n        attributes = new TreeMap<String, String>(attributes); // Make sure they\n        // are ordered.\n        if (transformation().getHtmlHeight() != null)\n            attributes.put(\"height\", transformation().getHtmlHeight());\n        if (transformation().getHtmlWidth() != null)\n            attributes.put(\"width\", transformation().getHtmlWidth());\n\n        boolean hiDPI = transformation().isHiDPI();\n        boolean responsive = transformation().isResponsive();\n\n        if (!config.clientHints && (hiDPI || responsive)) {\n            attributes.put(\"data-src\", url);\n            String extraClass = responsive ? \"cld-responsive\" : \"cld-hidpi\";\n            attributes.put(\"class\", (StringUtils.isBlank(attributes.get(\"class\")) ? \"\" : attributes.get(\"class\") + \" \") + extraClass);\n            String responsivePlaceholder = attributes.remove(\"responsive_placeholder\");\n            if (\"blank\".equals(responsivePlaceholder)) {\n                responsivePlaceholder = CL_BLANK;\n            }\n            url = responsivePlaceholder;\n        }\n\n        StringBuilder builder = new StringBuilder();\n        builder.append(\"<img\");\n        if (url != null) {\n            builder.append(\" src='\").append(url).append(\"'\");\n        }\n        for (Map.Entry<String, String> attr : attributes.entrySet()) {\n            builder.append(\" \").append(attr.getKey()).append(\"='\").append(attr.getValue()).append(\"'\");\n        }\n        builder.append(\"/>\");\n        return builder.toString();\n    }\n\n    public String videoTag() {\n        return videoTag(\"\", new HashMap<String, String>());\n    }\n\n    public String videoTag(String source) {\n        return videoTag(source, new HashMap<String, String>());\n    }\n\n    public String videoTag(Map<String, String> attributes) {\n        return videoTag(\"\", attributes);\n    }\n\n    private String finalizePosterUrl(String source) {\n        String posterUrl = null;\n        if (this.posterUrl != null) {\n            posterUrl = this.posterUrl.generate();\n        } else if (this.posterTransformation != null) {\n            posterUrl = this.clone().format(\"jpg\").transformation(new Transformation(this.posterTransformation))\n                    .generate(source);\n        } else if (this.posterSource != null) {\n            if (!StringUtils.isEmpty(this.posterSource))\n                posterUrl = this.clone().format(\"jpg\").generate(this.posterSource);\n        } else {\n            posterUrl = this.clone().format(\"jpg\").generate(source);\n        }\n        return posterUrl;\n    }\n\n    private void appendVideoSources(StringBuilder html, String source, String sourceType) {\n        Url sourceUrl = this.clone();\n        if (this.sourceTransformation != null) {\n            Transformation transformation = this.transformation;\n            Transformation sourceTransformation = null;\n            if (this.sourceTransformation.get(sourceType) != null)\n                sourceTransformation = new Transformation(this.sourceTransformation.get(sourceType));\n            if (transformation == null) {\n                transformation = sourceTransformation;\n            } else if (sourceTransformation != null) {\n                transformation = transformation.chainWith(sourceTransformation);\n            }\n            sourceUrl.transformation(transformation);\n        }\n        String src = sourceUrl.format(sourceType).generate(source);\n        String videoType = sourceType;\n        if (sourceType.equals(\"ogv\"))\n            videoType = \"ogg\";\n        String mimeType = \"video/\" + videoType;\n        html.append(\"<source src='\").append(src).append(\"' type='\").append(mimeType).append(\"'>\");\n    }\n\n    public String videoTag(String source, Map<String, String> attributes) {\n        if (StringUtils.isEmpty(source))\n            source = this.source;\n        if (StringUtils.isEmpty(source))\n            source = publicId;\n        if (StringUtils.isEmpty(source))\n            throw new IllegalArgumentException(\"must supply source or public id\");\n        source = VIDEO_EXTENSION_RE.matcher(source).replaceFirst(\"\");\n\n        if (this.resourceType == null) this.resourceType = \"video\";\n        attributes = new TreeMap<String, String>(attributes); // Make sure they are ordered.\n\n        String[] sourceTypes = this.sourceTypes;\n\n        if (sourceTypes == null) {\n            sourceTypes = DEFAULT_VIDEO_SOURCE_TYPES;\n        }\n\n        String posterUrl = this.finalizePosterUrl(source);\n\n        if (!StringUtils.isEmpty(posterUrl))\n            attributes.put(\"poster\", posterUrl);\n\n        StringBuilder html = new StringBuilder().append(\"<video\");\n\n        String url = null;\n\n        boolean multiSource = sourceTypes.length > 1;\n        if (!multiSource) {\n            url = generate(source + \".\" + sourceTypes[0]);\n            attributes.put(\"src\", url);\n        } else {\n            generate(source);\n        }\n\n        if (this.transformation.getHtmlHeight() != null)\n            attributes.put(\"height\", this.transformation.getHtmlHeight());\n        if (attributes.containsKey(\"html_height\"))\n            attributes.put(\"height\", attributes.remove(\"html_height\"));\n        if (this.transformation.getHtmlWidth() != null)\n            attributes.put(\"width\", this.transformation.getHtmlWidth());\n        if (attributes.containsKey(\"html_width\"))\n            attributes.put(\"width\", attributes.remove(\"html_width\"));\n\n        for (Map.Entry<String, String> attr : attributes.entrySet()) {\n            html.append(\" \").append(attr.getKey());\n            if (attr.getValue() != null) {\n                String value = ObjectUtils.asString(attr.getValue());\n                html.append(\"='\").append(value).append(\"'\");\n            }\n        }\n\n        html.append(\">\");\n\n        if (multiSource) {\n            for (String sourceType : sourceTypes) {\n                this.appendVideoSources(html, source, sourceType);\n            }\n        }\n\n        if (this.fallbackContent != null)\n            html.append(this.fallbackContent);\n        html.append(\"</video>\");\n        return html.toString();\n    }\n\n    public String generateSpriteCss(String source) {\n        this.type = \"sprite\";\n        if (!source.endsWith(\".css\"))\n            this.format = \"css\";\n        return generate(source);\n    }\n\n    public Url source(String source) {\n        this.source = source;\n        return this;\n    }\n\n    public Url source(StoredFile source) {\n        if (source.getResourceType() != null)\n            this.resourceType = source.getResourceType();\n        if (source.getType() != null)\n            this.type = source.getType();\n        if (source.getVersion() != null)\n            this.version = source.getVersion().toString();\n        this.format = source.getFormat();\n        this.source = source.getPublicId();\n        return this;\n    }\n}\n"
  },
  {
    "path": "cloudinary-core/src/main/java/com/cloudinary/Util.java",
    "content": "package com.cloudinary;\n\nimport com.cloudinary.utils.ObjectUtils;\nimport com.cloudinary.utils.StringUtils;\nimport org.cloudinary.json.JSONObject;\n\nimport java.security.MessageDigest;\nimport java.security.NoSuchAlgorithmException;\nimport java.util.*;\n\npublic final class Util {\n    private Util() {}\n\n    static final String[] BOOLEAN_UPLOAD_OPTIONS = new String[]{\"backup\", \"exif\", \"faces\", \"colors\", \"image_metadata\", \"use_filename\", \"unique_filename\",\n            \"eager_async\", \"invalidate\", \"discard_original_filename\", \"overwrite\", \"phash\", \"return_delete_token\", \"async\", \"quality_analysis\", \"cinemagraph_analysis\",\n            \"accessibility_analysis\", \"use_filename_as_display_name\", \"use_asset_folder_as_public_id_prefix\", \"unique_display_name\", \"media_metadata\", \"visual_search\",\n    \"auto_chaptering\", \"auto_transcription\"};\n\n    @SuppressWarnings({\"rawtypes\", \"unchecked\"})\n    public static final Map<String, Object> buildUploadParams(Map options) {\n        if (options == null)\n            options = ObjectUtils.emptyMap();\n        Map<String, Object> params = new HashMap<String, Object>();\n\n        params.put(\"public_id\", (String) options.get(\"public_id\"));\n        params.put(\"callback\", (String) options.get(\"callback\"));\n        params.put(\"format\", (String) options.get(\"format\"));\n        params.put(\"type\", (String) options.get(\"type\"));\n        for (String attr : BOOLEAN_UPLOAD_OPTIONS) {\n            putBoolean(attr, options, params);\n        }\n\n        params.put(\"eval\",(String) options.get(\"eval\"));\n        params.put(\"notification_url\", (String) options.get(\"notification_url\"));\n        params.put(\"eager_notification_url\", (String) options.get(\"eager_notification_url\"));\n        params.put(\"proxy\", (String) options.get(\"proxy\"));\n        params.put(\"folder\", (String) options.get(\"folder\"));\n        params.put(\"allowed_formats\", StringUtils.join(ObjectUtils.asArray(options.get(\"allowed_formats\")), \",\"));\n        params.put(\"moderation\", options.get(\"moderation\"));\n        params.put(\"access_mode\", (String) options.get(\"access_mode\"));\n        params.put(\"filename_override\", (String) options.get(\"filename_override\"));\n        params.put(\"public_id_prefix\", (String) options.get(\"public_id_prefix\"));\n        params.put(\"asset_folder\", (String) options.get(\"asset_folder\"));\n        params.put(\"display_name\", (String) options.get(\"display_name\"));\n        params.put(\"on_success\", (String) options.get(\"on_success\"));\n        Object responsive_breakpoints = options.get(\"responsive_breakpoints\");\n        if (responsive_breakpoints != null) {\n            params.put(\"responsive_breakpoints\", JSONObject.wrap(responsive_breakpoints));\n        }\n        params.put(\"upload_preset\", options.get(\"upload_preset\"));\n\n        if (options.get(\"signature\") == null) {\n            putEager(\"eager\", options, params);\n            Object transformation = options.get(\"transformation\");\n            if (transformation != null) {\n                if (transformation instanceof Transformation) {\n                    transformation = ((Transformation) transformation).generate();\n                }\n                params.put(\"transformation\", transformation.toString());\n            }\n            processWriteParameters(options, params);\n        } else {\n            // if there's a signature, it means all the params are already serialized so\n            // we don't need to construct them, just pass the value as is:\n            params.put(\"eager\", (String) options.get(\"eager\"));\n            params.put(\"transformation\", (String) options.get(\"transformation\"));\n            params.put(\"headers\", (String) options.get(\"headers\"));\n            params.put(\"tags\", (String) options.get(\"tags\"));\n            params.put(\"face_coordinates\", (String) options.get(\"face_coordinates\"));\n            params.put(\"context\", (String) options.get(\"context\"));\n            params.put(\"ocr\", (String) options.get(\"ocr\"));\n            params.put(\"raw_convert\", (String) options.get(\"raw_convert\"));\n            params.put(\"categorization\", (String) options.get(\"categorization\"));\n            params.put(\"detection\", (String) options.get(\"detection\"));\n            params.put(\"similarity_search\", (String) options.get(\"similarity_search\"));\n            params.put(\"auto_tagging\", (String) options.get(\"auto_tagging\"));\n            params.put(\"access_control\", (String) options.get(\"access_control\"));\n        }\n        return params;\n    }\n\n    public static Map buildMultiParams(Map options) {\n        Map<String, Object> params = new HashMap<String, Object>();\n\n        Object transformation = options.get(\"transformation\");\n        if (transformation != null) {\n            if (transformation instanceof Transformation) {\n                transformation = ((Transformation) transformation).generate();\n            }\n            params.put(\"transformation\", transformation.toString());\n        }\n        params.put(\"tag\", options.get(\"tag\"));\n        if (options.containsKey(\"urls\")) {\n            params.put(\"urls\", Arrays.asList((String[]) options.get(\"urls\")));\n        }\n        params.put(\"notification_url\", (String) options.get(\"notification_url\"));\n        params.put(\"format\", (String) options.get(\"format\"));\n        params.put(\"async\", ObjectUtils.asBoolean(options.get(\"async\"), false).toString());\n        params.put(\"mode\", options.get(\"mode\"));\n        putObject(\"timestamp\", options, params, Util.timestamp());\n\n        return params;\n    }\n\n    public static Map<String, Object> buildGenerateSpriteParams(Map options) {\n        HashMap<String, Object> params = new HashMap<String, Object>();\n        Object transParam = options.get(\"transformation\");\n        Transformation transformation = null;\n        if (transParam instanceof Transformation) {\n            transformation = new Transformation((Transformation) transParam);\n        } else if (transParam instanceof String) {\n            transformation = new Transformation().rawTransformation((String) transParam);\n        } else {\n            transformation = new Transformation();\n        }\n        String format = (String) options.get(\"format\");\n        if (format != null) {\n            transformation.fetchFormat(format);\n        }\n        params.put(\"transformation\", transformation.generate());\n        params.put(\"tag\", options.get(\"tag\"));\n        if (options.containsKey(\"urls\")) {\n            params.put(\"urls\", Arrays.asList((String[]) options.get(\"urls\")));\n        }\n        params.put(\"notification_url\", (String) options.get(\"notification_url\"));\n        params.put(\"async\", ObjectUtils.asBoolean(options.get(\"async\"), false).toString());\n        params.put(\"mode\", options.get(\"mode\"));\n        putObject(\"timestamp\", options, params, Util.timestamp());\n\n        return params;\n    }\n\n    protected static final String buildEager(List<? extends Transformation> transformations) {\n        if (transformations == null) {\n            return null;\n        }\n\n        List<String> eager = new ArrayList<String>();\n        for (Transformation transformation : transformations) {\n            String transformationString = transformation.generate();\n            if (StringUtils.isNotBlank(transformationString)) {\n                eager.add(transformationString);\n            }\n        }\n\n        return StringUtils.join(eager, \"|\");\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    public static final void processWriteParameters(Map<String, Object> options, Map<String, Object> params) {\n        if (options.get(\"headers\") != null)\n            params.put(\"headers\", buildCustomHeaders(options.get(\"headers\")));\n        if (options.get(\"tags\") != null)\n            params.put(\"tags\", StringUtils.join(ObjectUtils.asArray(options.get(\"tags\")), \",\"));\n        if (options.get(\"face_coordinates\") != null)\n            params.put(\"face_coordinates\", Coordinates.parseCoordinates(options.get(\"face_coordinates\")).toString());\n        if (options.get(\"custom_coordinates\") != null)\n            params.put(\"custom_coordinates\", Coordinates.parseCoordinates(options.get(\"custom_coordinates\")).toString());\n        if (options.get(\"context\") != null)\n            params.put(\"context\", encodeContext(options.get(\"context\")));\n        if (options.get(\"metadata\") != null)\n            params.put(\"metadata\", encodeContext(options.get(\"metadata\")));\n        if (options.get(\"access_control\") != null) {\n            params.put(\"access_control\", encodeAccessControl(options.get(\"access_control\")));\n        }\n        if (options.get(\"asset_folder\") != null) {\n            params.put(\"asset_folder\", options.get(\"asset_folder\"));\n        }\n        if (options.get(\"unique_display_name\") != null) {\n            params.put(\"unique_display_name\", options.get(\"unique_display_name\"));\n        }\n        if (options.get(\"display_name\") != null) {\n            params.put(\"display_name\", options.get(\"display_name\"));\n        }\n        putObject(\"ocr\", options, params);\n        putObject(\"raw_convert\", options, params);\n        putObject(\"categorization\", options, params);\n        putObject(\"detection\", options, params);\n        putObject(\"similarity_search\", options, params);\n        putObject(\"background_removal\", options, params);\n        if (options.get(\"auto_tagging\") != null) {\n            params.put(\"auto_tagging\", ObjectUtils.asFloat(options.get(\"auto_tagging\")));\n        }\n        if (options.get(\"clear_invalid\") != null) {\n            params.put(\"clear_invalid\", options.get(\"clear_invalid\"));\n        }\n        if(options.get(\"visual_search\") != null) {\n            params.put(\"visual_search\", options.get(\"visual_search\"));\n        }\n        if(options.get(\"auto_chaptering\") != null) {\n            params.put(\"auto_chaptering\", options.get(\"auto_chaptering\"));\n        }\n        if(options.get(\"auto_transcription\") != null) {\n            params.put(\"auto_transcription\", options.get(\"auto_transcription\"));\n        }\n    }\n\n    protected static String encodeAccessControl(Object accessControl) {\n        if (accessControl instanceof AccessControlRule) {\n            accessControl = Arrays.asList(accessControl);\n        }\n\n        return JSONObject.wrap(accessControl).toString();\n    }\n\n    protected static String encodeContext(Object context) {\n        if (context instanceof Map) {\n            Map<String, Object> mapArg = (Map<String, Object>) context;\n            HashSet out = new HashSet();\n            for (Map.Entry<String, Object> entry : mapArg.entrySet()) {\n                final String value;\n                if (entry.getValue() instanceof List) {\n                    value = encodeList(((List) entry.getValue()).toArray());\n                } else if (entry.getValue() instanceof String[]) {\n                    value = encodeList((String[]) entry.getValue());\n                } else {\n                    value = entry.getValue().toString();\n                }\n                out.add(entry.getKey() + \"=\" + encodeSingleContextString(value));\n            }\n            return StringUtils.join(out.toArray(), \"|\");\n        } else if (context == null) {\n            return null;\n        } else {\n            return context.toString();\n        }\n    }\n\n    private static String encodeList(Object[] list) {\n        StringBuilder builder = new StringBuilder(\"[\");\n\n        boolean first = true;\n        for (Object s : list) {\n            if (!first) {\n                builder.append(\",\");\n            }\n\n            builder.append(\"\\\"\").append(encodeSingleContextString(s.toString())).append(\"\\\"\");\n            first = false;\n        }\n\n        return builder.append(\"]\").toString();\n    }\n\n    private static String encodeSingleContextString(String value) {\n        return value.replaceAll(\"([=\\\\|])\", \"\\\\\\\\$1\");\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    protected static final String buildCustomHeaders(Object headers) {\n        if (headers == null) {\n            return null;\n        } else if (headers instanceof String) {\n            return (String) headers;\n        } else if (headers instanceof Object[]) {\n            return StringUtils.join((Object[]) headers, \"\\n\") + \"\\n\";\n        } else {\n            Map<String, String> headersMap = (Map<String, String>) headers;\n            StringBuilder builder = new StringBuilder();\n            for (Map.Entry<String, String> header : headersMap.entrySet()) {\n                builder.append(header.getKey()).append(\": \").append(header.getValue()).append(\"\\n\");\n            }\n            return builder.toString();\n        }\n    }\n\n    @SuppressWarnings(\"rawtypes\")\n    public static void clearEmpty(Map params) {\n        for (Iterator iterator = params.values().iterator(); iterator.hasNext(); ) {\n            Object value = iterator.next();\n            if (value == null || \"\".equals(value)) {\n                iterator.remove();\n            }\n        }\n    }\n\n    @SuppressWarnings({\"rawtypes\", \"unchecked\"})\n    public static final Map<String, Object> buildArchiveParams(Map options, String targetFormat) {\n        Map<String, Object> params = new HashMap<String, Object>();\n        if (options != null && options.size() > 0) {\n            params.put(\"type\", options.get(\"type\"));\n            params.put(\"mode\", options.get(\"mode\"));\n            params.put(\"target_format\", targetFormat);\n            params.put(\"target_public_id\", options.get(\"target_public_id\"));\n            putBoolean(\"flatten_folders\", options, params);\n            putBoolean(\"flatten_transformations\", options, params);\n            putBoolean(\"use_original_filename\", options, params);\n            putBoolean(\"async\", options, params);\n            putBoolean(\"keep_derived\", options, params);\n            params.put(\"notification_url\", options.get(\"notification_url\"));\n            putArray(\"target_tags\", options, params);\n            putArray(\"tags\", options, params);\n            putArray(\"public_ids\", options, params);\n            putArray(\"fully_qualified_public_ids\", options, params);\n            putArray(\"prefixes\", options, params);\n            putEager(\"transformations\", options, params);\n            putObject(\"timestamp\", options, params, Util.timestamp());\n            putBoolean(\"skip_transformation_name\", options, params);\n            putBoolean(\"allow_missing\", options, params);\n            putObject(\"expires_at\", options, params);\n        }\n        return params;\n    }\n\n    private static void putEager(String name, Map from, Map<String, Object> to) {\n        final Object transformations = from.get(name);\n        if (transformations != null)\n            to.put(name, buildEager((List<Transformation>) transformations));\n    }\n\n    private static void putBoolean(String name, Map from, Map<String, Object> to) {\n        final Object value = from.get(name);\n        if (value != null) {\n            to.put(name, ObjectUtils.asBoolean(value));\n        }\n    }\n\n    private static void putObject(String name, Map from, Map<String, Object> to) {\n        putObject(name, from, to, null);\n    }\n\n    private static void putObject(String name, Map from, Map<String, Object> to, Object defaultValue) {\n        final Object value = from.get(name);\n        if (value != null) {\n            to.put(name, value);\n        } else if (defaultValue != null) {\n            to.put(name, defaultValue);\n        }\n    }\n\n    private static void putArray(String name, Map from, Map<String, Object> to) {\n        final Object value = from.get(name);\n        if (value != null) {\n            to.put(name, ObjectUtils.asArray(value));\n        }\n    }\n\n    protected static String timestamp() {\n        return Long.toString(System.currentTimeMillis() / 1000L);\n    }\n\n    /**\n     * Encodes passed string value into a sequence of bytes using the UTF-8 charset.\n     *\n     * @param string string value to encode\n     * @return byte array representing passed string value\n     */\n    public static byte[] getUTF8Bytes(String string) {\n        try {\n            return string.getBytes(\"UTF-8\");\n        } catch (java.io.UnsupportedEncodingException e) {\n            throw new RuntimeException(\"Unexpected exception\", e);\n        }\n    }\n\n    /**\n     * Calculates signature, or hashed message authentication code (HMAC) of provided parameters name-value pairs and\n     * secret value using default hashing algorithm (SHA1).\n     * <p>\n     * Argument for hashing function is built by joining sorted parameter name-value pairs into single string in the\n     * same fashion as HTTP GET method uses, and concatenating the result with secret value in the end. Method supports\n     * arrays/collections as parameter values. In this case, the elements of array/collection are joined into single\n     * comma-delimited string prior to inclusion into the result.\n     *\n     * @param paramsToSign  parameter name-value pairs list represented as instance of {@link Map}\n     * @param apiSecret     secret value\n     * @return hex-string representation of signature calculated based on provided parameters map and secret\n     */\n    public static String produceSignature(Map<String, Object> paramsToSign, String apiSecret, int signatureVersion) {\n        return produceSignature(paramsToSign, apiSecret, SignatureAlgorithm.SHA1, signatureVersion);\n    }\n\n    /**\n     * Calculates signature, or hashed message authentication code (HMAC) of provided parameters name-value pairs and\n     * secret value using specified hashing algorithm.\n     * <p>\n     * Argument for hashing function is built by joining sorted parameter name-value pairs into single string in the\n     * same fashion as HTTP GET method uses, and concatenating the result with secret value in the end. Method supports\n     * arrays/collections as parameter values. In this case, the elements of array/collection are joined into single\n     * comma-delimited string prior to inclusion into the result.\n     *\n     * @param paramsToSign  parameter name-value pairs list represented as instance of {@link Map}\n     * @param apiSecret     secret value\n     * @param signatureAlgorithm type of hashing algorithm to use for calculation of HMAC\n     * @return hex-string representation of signature calculated based on provided parameters map and secret\n     */\n    public static String produceSignature(Map<String, Object> paramsToSign, String apiSecret, SignatureAlgorithm signatureAlgorithm, int signatureVersion) {\n        Collection<String> flattenedParams = flattenAndSanitizeParams(paramsToSign, signatureVersion);\n        String toSign = StringUtils.join(flattenedParams, \"&\") + apiSecret;\n        byte[] hash = Util.hash(toSign, signatureAlgorithm);\n        return StringUtils.encodeHexString(hash);\n    }\n\n    private static Collection<String> flattenAndSanitizeParams(Map<String, Object> paramsToSign, int signatureVersion) {\n        Collection<String> params = new ArrayList<>();\n\n        for (Map.Entry<String, Object> entry : new TreeMap<>(paramsToSign).entrySet()) {\n            Object value = entry.getValue();\n            String rawValue = null;\n\n            if (value instanceof Collection) {\n                rawValue = StringUtils.join((Collection) value, \",\");\n            } else if (value instanceof Object[]) {\n                rawValue = StringUtils.join((Object[]) value, \",\");\n            } else if (value != null && StringUtils.isNotBlank(value.toString())) {\n                rawValue = value.toString();\n            }\n\n            if (rawValue != null) {\n                String sanitizedValue = (signatureVersion == 2)\n                        ? escapeAmpersand(rawValue)\n                        : rawValue;\n\n                params.add(entry.getKey() + \"=\" + sanitizedValue);\n            }\n        }\n\n        return params;\n    }\n\n    private static String escapeAmpersand(String input) {\n        return input.replace(\"&\", \"%26\");\n    }\n\n    /**\n     * Computes hash from input string using specified algorithm.\n     * \n     * @param input              string which to compute hash from\n     * @param signatureAlgorithm algorithm to use for computing hash\n     * @return array of bytes of computed hash value\n     */\n    public static byte[] hash(String input, SignatureAlgorithm signatureAlgorithm) {\n        try {\n            return MessageDigest.getInstance(signatureAlgorithm.getAlgorithmId()).digest(Util.getUTF8Bytes(input));\n        } catch (NoSuchAlgorithmException e) {\n            throw new RuntimeException(\"Unexpected exception\", e);\n        }\n    }\n}\n"
  },
  {
    "path": "cloudinary-core/src/main/java/com/cloudinary/api/ApiResponse.java",
    "content": "package com.cloudinary.api;\n\nimport java.text.ParseException;\nimport java.util.Map;\n\n@SuppressWarnings(\"rawtypes\")\npublic interface ApiResponse extends Map {\n    Map<String, RateLimit> rateLimits() throws ParseException;\n\n    RateLimit apiRateLimit() throws ParseException;\n}\n"
  },
  {
    "path": "cloudinary-core/src/main/java/com/cloudinary/api/AuthorizationRequired.java",
    "content": "package com.cloudinary.api;\n\nimport com.cloudinary.api.exceptions.ApiException;\n\npublic class AuthorizationRequired extends ApiException {\n    private static final long serialVersionUID = 7160740370855761014L;\n\n    public AuthorizationRequired(String message) {\n        super(message);\n    }\n}"
  },
  {
    "path": "cloudinary-core/src/main/java/com/cloudinary/api/RateLimit.java",
    "content": "package com.cloudinary.api;\n\nimport java.util.Date;\n\npublic class RateLimit {\n    private long limit = 0L;\n    private long remaining = 0L;\n    private Date reset = null;\n\n    public RateLimit() {\n        super();\n    }\n\n    public long getLimit() {\n        return limit;\n    }\n\n    public void setLimit(long limit) {\n        this.limit = limit;\n    }\n\n    public long getRemaining() {\n        return remaining;\n    }\n\n    public void setRemaining(long remaining) {\n        this.remaining = remaining;\n    }\n\n    public Date getReset() {\n        return reset;\n    }\n\n    public void setReset(Date reset) {\n        this.reset = reset;\n    }\n}\n"
  },
  {
    "path": "cloudinary-core/src/main/java/com/cloudinary/api/exceptions/AlreadyExists.java",
    "content": "package com.cloudinary.api.exceptions;\n\npublic class AlreadyExists extends ApiException {\n    private static final long serialVersionUID = 999568182896607322L;\n\n    public AlreadyExists(String message) {\n        super(message);\n    }\n}"
  },
  {
    "path": "cloudinary-core/src/main/java/com/cloudinary/api/exceptions/ApiException.java",
    "content": "package com.cloudinary.api.exceptions;\n\npublic class ApiException extends Exception {\n    private static final long serialVersionUID = 4416861825144420038L;\n\n    public ApiException(String message) {\n        super(message);\n    }\n}\n"
  },
  {
    "path": "cloudinary-core/src/main/java/com/cloudinary/api/exceptions/BadRequest.java",
    "content": "package com.cloudinary.api.exceptions;\n\n\npublic class BadRequest extends ApiException {\n    private static final long serialVersionUID = 1410136354253339531L;\n\n    public BadRequest(String message) {\n        super(message);\n    }\n}"
  },
  {
    "path": "cloudinary-core/src/main/java/com/cloudinary/api/exceptions/GeneralError.java",
    "content": "package com.cloudinary.api.exceptions;\n\npublic class GeneralError extends ApiException {\n    private static final long serialVersionUID = 4553362706625067182L;\n\n    public GeneralError(String message) {\n        super(message);\n    }\n}"
  },
  {
    "path": "cloudinary-core/src/main/java/com/cloudinary/api/exceptions/NotAllowed.java",
    "content": "package com.cloudinary.api.exceptions;\n\n\npublic class NotAllowed extends ApiException {\n    private static final long serialVersionUID = 4371365822491647653L;\n\n    public NotAllowed(String message) {\n        super(message);\n    }\n}"
  },
  {
    "path": "cloudinary-core/src/main/java/com/cloudinary/api/exceptions/NotFound.java",
    "content": "package com.cloudinary.api.exceptions;\n\n\npublic class NotFound extends ApiException {\n    private static final long serialVersionUID = -2072640462778940357L;\n\n    public NotFound(String message) {\n        super(message);\n    }\n}"
  },
  {
    "path": "cloudinary-core/src/main/java/com/cloudinary/api/exceptions/RateLimited.java",
    "content": "package com.cloudinary.api.exceptions;\n\n\npublic class RateLimited extends ApiException {\n    private static final long serialVersionUID = -8298038106172355219L;\n\n    public RateLimited(String message) {\n        super(message);\n    }\n}\n"
  },
  {
    "path": "cloudinary-core/src/main/java/com/cloudinary/api/signing/ApiResponseSignatureVerifier.java",
    "content": "package com.cloudinary.api.signing;\n\nimport com.cloudinary.SignatureAlgorithm;\nimport com.cloudinary.Util;\nimport com.cloudinary.utils.ObjectUtils;\nimport com.cloudinary.utils.StringUtils;\n\nimport static com.cloudinary.utils.StringUtils.emptyIfNull;\n\n/**\n * The {@code ApiResponseSignatureVerifier} class is responsible for verifying Cloudinary Upload API response signatures.\n */\npublic class ApiResponseSignatureVerifier {\n    private final String secretKey;\n    private final SignatureAlgorithm signatureAlgorithm;\n\n    /**\n     * Initializes new instance of {@code ApiResponseSignatureVerifier} class with a secret key required to perform\n     * API response signatures verification.\n     *\n     * @param secretKey shared secret key string which is used to sign and verify authenticity of API responses\n     */\n    public ApiResponseSignatureVerifier(String secretKey) {\n        if (StringUtils.isBlank(secretKey)) {\n            throw new IllegalArgumentException(\"Secret key is required\");\n        }\n\n        this.secretKey = secretKey;\n        this.signatureAlgorithm = SignatureAlgorithm.SHA1;\n    }\n\n    /**\n     * Initializes new instance of {@code ApiResponseSignatureVerifier} class with a secret key required to perform\n     * API response signatures verification.\n     *\n     * @param secretKey shared secret key string which is used to sign and verify authenticity of API responses\n     * @param signatureAlgorithm type of hashing algorithm to use for calculation of HMACs\n     */\n    public ApiResponseSignatureVerifier(String secretKey, SignatureAlgorithm signatureAlgorithm) {\n        if (StringUtils.isBlank(secretKey)) {\n            throw new IllegalArgumentException(\"Secret key is required\");\n        }\n\n        this.secretKey = secretKey;\n        this.signatureAlgorithm = signatureAlgorithm;\n    }\n\n    /**\n     * Checks whether particular Cloudinary Upload API response signature matches expected signature.\n     *\n     * The task is performed by generating signature using same hashing algorithm as used on Cloudinary servers and\n     * comparing the result with provided actual signature.\n     *\n     * @param publicId public id of uploaded resource as stated in upload API response\n     * @param version version of uploaded resource as stated in upload API response\n     * @param signature signature of upload API response, usually passed via X-Cld-Signature custom HTTP response header\n     *\n     * @return true if response signature passed verification procedure\n     */\n    public boolean verifySignature(String publicId, String version, String signature) {\n        return Util.produceSignature(ObjectUtils.asMap(\n                \"public_id\", emptyIfNull(publicId),\n                \"version\", emptyIfNull(version)), secretKey, signatureAlgorithm, 1).equals(signature);\n    }\n}\n"
  },
  {
    "path": "cloudinary-core/src/main/java/com/cloudinary/api/signing/NotificationRequestSignatureVerifier.java",
    "content": "package com.cloudinary.api.signing;\n\nimport com.cloudinary.SignatureAlgorithm;\n\nimport static com.cloudinary.utils.StringUtils.emptyIfNull;\n\n/**\n * The {@code NotificationRequestSignatureVerifier} class is responsible for verifying authenticity and integrity\n * of Cloudinary Upload notifications.\n */\npublic class NotificationRequestSignatureVerifier {\n    private final SignedPayloadValidator signedPayloadValidator;\n\n    /**\n     * Initializes new instance of {@code NotificationRequestSignatureVerifier} with secret key value.\n     *\n     * @param secretKey shared secret key string which is used to sign and verify authenticity of notifications\n     */\n    public NotificationRequestSignatureVerifier(String secretKey) {\n        this.signedPayloadValidator = new SignedPayloadValidator(secretKey, SignatureAlgorithm.SHA1);\n    }\n\n    /**\n     * Initializes new instance of {@code NotificationRequestSignatureVerifier} with secret key value.\n     *\n     * @param secretKey shared secret key string which is used to sign and verify authenticity of notifications\n     * @param signatureAlgorithm type of hashing algorithm to use for calculation of HMACs\n     */\n    public NotificationRequestSignatureVerifier(String secretKey, SignatureAlgorithm signatureAlgorithm) {\n        this.signedPayloadValidator = new SignedPayloadValidator(secretKey, signatureAlgorithm);\n    }\n\n    /**\n     * Verifies signature of Cloudinary Upload notification.\n     *\n     * @param body      notification message body, represented as string\n     * @param timestamp value of X-Cld-Timestamp custom HTTP header of notification message, representing notification\n     *                  issue timestamp\n     * @param signature actual signature value, usually passed via X-Cld-Signature custom HTTP header of notification\n     *                  message\n     * @return true if notification passed verification procedure\n     */\n    public boolean verifySignature(String body, String timestamp, String signature) {\n        return signedPayloadValidator.validateSignedPayload(\n                emptyIfNull(body) + emptyIfNull(timestamp),\n                signature);\n    }\n\n    /**\n     * Verifies signature of Cloudinary Upload notification.\n     * <p>\n     * Differs from {@link #verifySignature(String, String, String)} in additional validation which consists of making\n     * sure the notification being verified is still not expired based on timestamp parameter value.\n     *\n     * @param body            notification message body, represented as string\n     * @param timestamp       value of X-Cld-Timestamp custom HTTP header of notification message, representing notification\n     *                        issue timestamp in seconds\n     * @param signature       actual signature value, usually passed via X-Cld-Signature custom HTTP header of notification\n     *                        message\n     * @param secondsValidFor the amount of time, in seconds, the notification message is considered valid by client\n     * @return true if notification passed verification procedure\n     */\n    public boolean verifySignature(String body, String timestamp, String signature, long secondsValidFor) {\n        long parsedTimestamp;\n        try {\n            parsedTimestamp = Long.parseLong(timestamp);\n        } catch (NumberFormatException e) {\n            throw new IllegalArgumentException(\"Provided timestamp is not a valid number\", e);\n        }\n\n        return verifySignature(body, timestamp, signature) &&\n                (System.currentTimeMillis() / 1000L - parsedTimestamp <= secondsValidFor);\n    }\n\n}\n"
  },
  {
    "path": "cloudinary-core/src/main/java/com/cloudinary/api/signing/SignedPayloadValidator.java",
    "content": "package com.cloudinary.api.signing;\n\nimport com.cloudinary.SignatureAlgorithm;\nimport com.cloudinary.Util;\nimport com.cloudinary.utils.StringUtils;\n\nimport static com.cloudinary.utils.StringUtils.emptyIfNull;\n\nclass SignedPayloadValidator {\n    private final String secretKey;\n    private final SignatureAlgorithm signatureAlgorithm;\n\n    SignedPayloadValidator(String secretKey, SignatureAlgorithm signatureAlgorithm) {\n        if (StringUtils.isBlank(secretKey)) {\n            throw new IllegalArgumentException(\"Secret key is required\");\n        }\n\n        this.secretKey = secretKey;\n        this.signatureAlgorithm = signatureAlgorithm;\n    }\n\n    boolean validateSignedPayload(String signedPayload, String signature) {\n        String expectedSignature =\n                StringUtils.encodeHexString(Util.hash(emptyIfNull(signedPayload) + secretKey,\n                        signatureAlgorithm));\n\n        return expectedSignature.equals(signature);\n    }\n}\n"
  },
  {
    "path": "cloudinary-core/src/main/java/com/cloudinary/api/signing/package-info.java",
    "content": "/**\n * The package holds classes used internally to implement verification procedures of authenticity and integrity of\n * client communication with Cloudinary servers. Verification is in most cases based on calculating and comparing so called\n * signatures, or hashed message authentication codes (HMAC) - string values calculated based on message payload, some\n * secret key value shared between communicating parties and agreed hashing function.\n */\npackage com.cloudinary.api.signing;"
  },
  {
    "path": "cloudinary-core/src/main/java/com/cloudinary/metadata/DateMetadataField.java",
    "content": "package com.cloudinary.metadata;\n\nimport com.cloudinary.utils.ObjectUtils;\n\nimport java.text.ParseException;\nimport java.util.Date;\n\n/**\n * Represents a metadata field with type 'date'\n */\npublic class DateMetadataField extends MetadataField<Date> {\n\n    public DateMetadataField() {\n        super(MetadataFieldType.DATE);\n    }\n\n    /**\n     * Sets the default date used for this field.\n     * @param defaultValue The date to set. Date only without a time component, UTC assumed.\n     */\n    @Override\n    public void setDefaultValue(Date defaultValue) {\n        put(DEFAULT_VALUE, ObjectUtils.toISO8601DateOnly(defaultValue));\n    }\n\n    /**\n     * Get the default value of this date field.\n     * @return The date only without a time component, UTC.\n     * @throws ParseException When the underlying value is malformed.\n     */\n    @Override\n    public Date getDefaultValue() throws ParseException {\n        Object value = get(DEFAULT_VALUE);\n        if (value == null) {\n            return null;\n        }\n\n        return ObjectUtils.fromISO8601DateOnly(value.toString());\n    }\n}\n"
  },
  {
    "path": "cloudinary-core/src/main/java/com/cloudinary/metadata/EnumMetadataField.java",
    "content": "package com.cloudinary.metadata;\n\n/**\n * Represents a metadata field with 'Enum' type.\n */\npublic class EnumMetadataField extends MetadataField<String> {\n    EnumMetadataField() {\n        super(MetadataFieldType.ENUM);\n    }\n}\n"
  },
  {
    "path": "cloudinary-core/src/main/java/com/cloudinary/metadata/IntMetadataField.java",
    "content": "package com.cloudinary.metadata;\n\n/**\n * Represents a metadata field with 'Int' type.\n */\npublic class IntMetadataField extends MetadataField<Integer> {\n    public IntMetadataField() {\n        super(MetadataFieldType.INTEGER);\n    }\n}\n"
  },
  {
    "path": "cloudinary-core/src/main/java/com/cloudinary/metadata/MetadataDataSource.java",
    "content": "package com.cloudinary.metadata;\n\nimport org.cloudinary.json.JSONArray;\nimport org.cloudinary.json.JSONObject;\n\nimport java.util.List;\n\n/**\n * Represent a data source for a given field. This is used in both 'Set' and 'Enum' field types.\n * The datasource holds a list of the valid values to be used with the corresponding metadata field.\n */\npublic class MetadataDataSource extends JSONObject {\n    /**\n     * Creates a new instance of data source with the given list of entries.\n     * @param entries\n     */\n    public MetadataDataSource(List<Entry> entries) {\n        put(\"values\", new JSONArray(entries.toArray()));\n    }\n\n    /**\n     * Represents a single entry in a datasource definition for a field.\n     */\n    public static class Entry extends JSONObject {\n        public Entry(String externalId, String value){\n            setExternalId(externalId);\n            setValue(value);\n        }\n\n        /**\n         * Create a new entry with a string value.\n         * @param value The value to use in the entry.\n         */\n        public Entry(String value){\n            this(null, value);\n        }\n\n        /**\n         * Set the id of the entry. Will be auto-generated if left blank.\n         * @param externalId\n         */\n        public void setExternalId(String externalId) {\n            put(\"external_id\", externalId);\n        }\n\n        /**\n         * Get the id of the entry.\n         * @return\n         */\n        public String getExternalId() {\n            return optString(\"external_id\");\n        }\n\n        /**\n         * Set the value of the entry.\n         * @param value The value to set.\n         */\n        public void setValue(String value) {\n            put(\"value\", value);\n        }\n\n        /**\n         * Get the value of the entry.\n         * @return The value.\n         */\n        public String getValue() {\n            return optString(\"value\");\n        }\n    }\n}\n"
  },
  {
    "path": "cloudinary-core/src/main/java/com/cloudinary/metadata/MetadataField.java",
    "content": "package com.cloudinary.metadata;\n\nimport org.cloudinary.json.JSONObject;\n\nimport java.text.ParseException;\n\n/**\n * Represents a single metadata field. Use one of the derived classes in the metadata API calls.\n * @param <T>\n */\npublic class MetadataField<T> extends JSONObject {\n\n    public static final String DEFAULT_VALUE = \"default_value\";\n    public static final String EXTERNAL_ID = \"external_id\";\n    public static final String LABEL = \"label\";\n    public static final String MANDATORY = \"mandatory\";\n    public static final String TYPE = \"type\";\n    public static final String VALIDATION = \"validation\";\n    public static final String RESTRICTIONS = \"restrictions\";\n    public static final String DEFAULT_DISABLED = \"default_disabled\";\n    public static final String ALLOW_DYNAMIC_LIST_VALUES = \"allow_dynamic_list_values\";\n\n    public MetadataField(MetadataFieldType type) {\n        put(TYPE, type.toString());\n    }\n\n    public MetadataField(String type) {\n        put(TYPE, type);\n    }\n\n    /**\n     * The type of the field.\n     * @return String with the name of the type.\n     */\n    public MetadataFieldType getType() {\n        return MetadataFieldType.valueOf(optString(TYPE).toUpperCase());\n    }\n\n    /**\n     * Get the id of the field.\n     * @return String, field id.\n     */\n    public String getExternalId() {\n        return optString(EXTERNAL_ID);\n    }\n\n    /**\n     * Set the id of the string (auto-generated if this is left blank).\n     * @param externalId The id to set.\n     */\n    public void setExternalId(String externalId) {\n        put(EXTERNAL_ID, externalId);\n    }\n\n    /**\n     * Get the label of the field\n     * @return String, the label of the field.\n     */\n    public String getLabel() {\n        return optString(LABEL);\n    }\n\n    /**\n     * Sets the label of the field\n     * @param label The label to set.\n     */\n    public void setLabel(String label) {\n        put(LABEL, label);\n    }\n\n    /**\n     * Cehcks whether the field is mandatory.\n     * @return Boolean indicating whether the field is mandatory.\n     */\n    public boolean isMandatory() {\n        return optBoolean(MANDATORY);\n    }\n\n    /**\n     * Sets a boolean indicating whether this fields needs to be mandatory.\n     * @param mandatory The boolean to set.\n     */\n    public void setMandatory(Boolean mandatory) {\n        put(MANDATORY, mandatory);\n    }\n\n    /**\n     * Gets the default value of this field.\n     * @return The default value\n     * @throws ParseException If the stored value can't be parsed to the correct type.\n     */\n    public T getDefaultValue() throws ParseException {\n        //noinspection unchecked\n        return (T)opt(DEFAULT_VALUE);\n    }\n\n    /**\n     * Set the default value of the field\n     * @param defaultValue The value to set.\n     */\n    public void setDefaultValue(T defaultValue) {\n        put(DEFAULT_VALUE, defaultValue);\n    }\n\n    /**\n     * Get the validation rules of this field.\n     * @return The validation rules.\n     */\n    public MetadataValidation getValidation() {\n        return (MetadataValidation) optJSONObject(VALIDATION);\n    }\n\n    /**\n     * Set the validation rules of this field.\n     * @param validation The rules to set.\n     */\n    public void setValidation(MetadataValidation validation) {\n        put(VALIDATION, validation);\n    }\n\n    /**\n     * Get the data source definition of this field.\n     * @return The data source.\n     */\n    public MetadataDataSource getDataSource() {\n        return (MetadataDataSource) optJSONObject(\"datasource\");\n    }\n\n    /**\n     * Set the datasource for the field.\n     * @param dataSource The datasource to set.\n     */\n    public void setDataSource(MetadataDataSource dataSource) {\n        put(\"datasource\", dataSource);\n    }\n\n    /**\n     * Set the restrictions rules of this field.\n     * @param restrictions The rules to set.\n     */\n    public void setRestrictions(Restrictions restrictions) {\n        put(RESTRICTIONS, restrictions.toHash());\n    }\n\n    /**\n     * Set the value indicating whether the field should be disabled by default\n     * @param disabled The value to set.\n     */\n    public void setDefaultDisabled(Boolean disabled) {\n        put(DEFAULT_DISABLED, disabled);\n    }\n\n    /**\n     * Set the value indicating whether the dynamic list values should allow\n     * @param allowDynamicListValues The value to set.\n     */\n    public void setAllowDynamicListValues(Boolean allowDynamicListValues) {put(ALLOW_DYNAMIC_LIST_VALUES, allowDynamicListValues);}\n}"
  },
  {
    "path": "cloudinary-core/src/main/java/com/cloudinary/metadata/MetadataFieldType.java",
    "content": "package com.cloudinary.metadata;\n\n/**\n * Enum represneting all the valid field types.\n */\npublic enum MetadataFieldType  {\n    STRING,\n    INTEGER,\n    DATE,\n    ENUM,\n    SET;\n\n    @Override\n    public String toString() {\n        return super.toString().toLowerCase();\n    }\n}\n"
  },
  {
    "path": "cloudinary-core/src/main/java/com/cloudinary/metadata/MetadataRule.java",
    "content": "package com.cloudinary.metadata;\n\nimport com.cloudinary.utils.ObjectUtils;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\npublic class MetadataRule {\n    String metadataFieldId;\n    String name;\n    MetadataRuleCondition condition;\n    MetadataRuleResult result;\n\n    public MetadataRule(String metadataFieldId, String name, MetadataRuleCondition condition, MetadataRuleResult result) {\n        this.metadataFieldId = metadataFieldId;\n        this.name = name;\n        this.condition = condition;\n        this.result = result;\n    }\n\n    public String getMetadataFieldId() {\n        return metadataFieldId;\n    }\n\n    public void setMetadataFieldId(String metadataFieldId) {\n        this.metadataFieldId = metadataFieldId;\n    }\n\n    public String getName() {\n        return name;\n    }\n\n    public void setName(String name) {\n        this.name = name;\n    }\n\n    public MetadataRuleCondition getCondition() {\n        return condition;\n    }\n\n    public void setCondition(MetadataRuleCondition condition) {\n        this.condition = condition;\n    }\n\n    public MetadataRuleResult getResult() {\n        return result;\n    }\n\n    public void setResult(MetadataRuleResult result) {\n        this.result = result;\n    }\n\n    public Map asMap() {\n        Map map = new HashMap();\n        map.put(\"metadata_field_id\", getMetadataFieldId());\n        map.put(\"name\", getName());\n        if (getCondition() != null) {\n            map.put(\"condition\", ObjectUtils.toJSON(getCondition().asMap()));\n        }\n        if(getResult() != null) {\n            map.put(\"result\", ObjectUtils.toJSON(getResult().asMap()));\n        }\n        return map;\n    }\n}\n"
  },
  {
    "path": "cloudinary-core/src/main/java/com/cloudinary/metadata/MetadataRuleCondition.java",
    "content": "package com.cloudinary.metadata;\nimport java.util.HashMap;\nimport java.util.Map;\n\npublic class MetadataRuleCondition {\n    String metadata_field_id;\n    Boolean populated;\n    Map<String, String> includes;\n    String equals;\n\n    public MetadataRuleCondition(String metadata_field_id, Boolean populated, Map<String, String> includes, String equals) {\n        this.metadata_field_id = metadata_field_id;\n        this.populated = populated;\n        this.includes = includes;\n        this.equals = equals;\n    }\n\n    public String getMetadata_field_id() {\n        return metadata_field_id;\n    }\n\n    public void setMetadata_field_id(String metadata_field_id) {\n        this.metadata_field_id = metadata_field_id;\n    }\n\n    public Boolean getPopulated() {\n        return populated;\n    }\n\n    public void setPopulated(Boolean populated) {\n        this.populated = populated;\n    }\n\n    public Map<String, String> getIncludes() {\n        return includes;\n    }\n\n    public void setIncludes(Map<String, String> includes) {\n        this.includes = includes;\n    }\n\n    public String getEquals() {\n        return equals;\n    }\n\n    public void setEquals(String equals) {\n        this.equals = equals;\n    }\n\n    public Map asMap() {\n        Map result = new HashMap(4);\n        result.put(\"metadata_field_id\", metadata_field_id);\n        result.put(\"populated\", populated);\n        result.put(\"includes\", includes);\n        result.put(\"equals\", equals);\n        return result;\n    }\n}\n"
  },
  {
    "path": "cloudinary-core/src/main/java/com/cloudinary/metadata/MetadataRuleResult.java",
    "content": "package com.cloudinary.metadata;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\npublic class MetadataRuleResult {\n    Boolean enabled;\n    String activateValues;\n    String applyValues;\n    Boolean setMandatory;\n\n    public MetadataRuleResult(Boolean enabled, String activateValues, String applyValues, Boolean setMandatory) {\n        this.enabled = enabled;\n        this.activateValues = activateValues;\n        this.applyValues = applyValues;\n        this.setMandatory = setMandatory;\n    }\n\n    public Boolean getEnabled() {\n        return enabled;\n    }\n\n    public void setEnabled(Boolean enabled) {\n        this.enabled = enabled;\n    }\n\n    public String getActivateValues() {\n        return activateValues;\n    }\n\n    public void setActivateValues(String activateValues) {\n        this.activateValues = activateValues;\n    }\n\n    public String getApplyValues() {\n        return applyValues;\n    }\n\n    public void setApplyValues(String applyValues) {\n        this.applyValues = applyValues;\n    }\n\n    public Boolean getSetMandatory() {\n        return setMandatory;\n    }\n\n    public void setSetMandatory(Boolean setMandatory) {\n        this.setMandatory = setMandatory;\n    }\n    public Map asMap() {\n        Map result = new HashMap(4);\n        result.put(\"enable\", enabled);\n        result.put(\"activate_values\", activateValues);\n        result.put(\"apply_values\", applyValues);\n        result.put(\"mandatory\", setMandatory);\n        return result;\n    }\n}\n"
  },
  {
    "path": "cloudinary-core/src/main/java/com/cloudinary/metadata/MetadataValidation.java",
    "content": "package com.cloudinary.metadata;\n\nimport com.cloudinary.utils.ObjectUtils;\nimport org.cloudinary.json.JSONArray;\nimport org.cloudinary.json.JSONObject;\n\nimport java.util.Date;\nimport java.util.List;\n\n/**\n * Represents the base class for metadata fields validation mechanisms.\n */\npublic abstract class MetadataValidation extends JSONObject {\n\n    public static final String TYPE = \"type\";\n    public static final String MIN = \"min\";\n    public static final String MAX = \"max\";\n    public static final String STRLEN = \"strlen\";\n    public static final String EQUALS = \"equals\";\n    public static final String GREATER_THAN = \"greater_than\";\n    public static final String LESS_THAN = \"less_than\";\n    public static final String VALUE = \"value\";\n\n    /**\n     * An 'And' rule validation used to combine other rules with an 'AND' logic relation between them.\n     */\n    public static class AndValidator extends MetadataValidation {\n\n        public static final String AND = \"and\";\n\n        /**\n         * Create a new instance of the validator with the given rules.\n         * @param rules The rules to use.\n         */\n        public AndValidator(List<MetadataValidation> rules) {\n            put(TYPE, AND);\n            put(\"rules\", new JSONArray(rules.toArray()));\n        }\n    }\n\n    /**\n     * A validator to validate string lengths\n     */\n    public static class StringLength extends MetadataValidation {\n        /**\n         * Create a new instance with the given min and max.\n         * @param min Minimum valid string length.\n         * @param max Maximum valid string length.\n         */\n        public StringLength(Integer min, Integer max) {\n            put(TYPE, STRLEN);\n            put(MIN, min);\n            put(MAX, max);\n        }\n    }\n\n    /**\n     * Base class for all comparison (greater than/less than) validation rules.\n     * @param <T>\n     */\n     public abstract static class ComparisonRule<T> extends MetadataValidation {\n        public ComparisonRule(String type, T value) {\n            this(type, value, null);\n        }\n\n        public ComparisonRule(String type, T value, Boolean equals) {\n            put(TYPE, type);\n            putValue(value);\n            if (equals != null) {\n                put(EQUALS, equals);\n            }\n        }\n\n        protected void putValue(T value) {\n            put(VALUE, value);\n        }\n    }\n\n    /**\n     * Great-than rule for integers.\n     */\n    public static class IntGreaterThan extends ComparisonRule<Integer> {\n        /**\n         * Create a new rule with the given integer.\n         * @param value The integer to reference in the rule\n         */\n        public IntGreaterThan(Integer value) {\n            super(GREATER_THAN, value);\n        }\n\n        /**\n         * Create a new rule with the given integer.\n         * @param value The integer to reference in the rule.\n         * @param equals Whether a field value equal to the rule value is considered valid.\n         */\n        public IntGreaterThan(Integer value, Boolean equals) {\n            super(GREATER_THAN, value, equals);\n        }\n    }\n\n    /**\n     * Great-than rule for dates.\n     */\n    public static class DateGreaterThan extends ComparisonRule<Date> {\n        /**\n         * Create a new rule with the given date.\n         * @param value The integer to reference in the rule\n         */\n        public DateGreaterThan(Date value) {\n            super(GREATER_THAN, value);\n        }\n\n        /**\n         * Create a new rule with the given date.\n         * @param value The date to reference in the rule.\n         * @param equals Whether a field value equal to the rule value is considered valid.\n         */\n        public DateGreaterThan(Date value, Boolean equals) {\n            super(GREATER_THAN, value, equals);\n        }\n\n        @Override\n        protected void putValue(Date value) {\n            put(VALUE, ObjectUtils.toISO8601DateOnly(value));\n        }\n    }\n\n    /**\n     * Less-than rule for integers.\n     */\n    public static class IntLessThan extends ComparisonRule<Integer> {\n        /**\n         * Create a new rule with the given integer.\n         * @param value The integer to reference in the rule\n         */\n        public IntLessThan(Integer value) {\n            super(LESS_THAN, value);\n        }\n\n        /**\n         * Create a new rule with the given integer.\n         * @param value The integer to reference in the rule.\n         * @param equals Whether a field value equal to the rule value is considered valid.\n         */\n        public IntLessThan(Integer value, Boolean equals) {\n            super(LESS_THAN, value, equals);\n        }\n    }\n\n    /**\n     * Less-than rule for dates.\n     */\n    public static class DateLessThan extends ComparisonRule<Date> {\n        /**\n         * Create a new rule with the given date.\n         * @param value The integer to reference in the rule\n         */\n        public DateLessThan(Date value) {\n            super(LESS_THAN, value);\n        }\n\n        /**\n         * Create a new rule with the given date.\n         * @param value The date to reference in the rule.\n         * @param equals Whether a field value equal to the rule value is considered valid.\n         */\n        public DateLessThan(Date value, Boolean equals) {\n            super(LESS_THAN, value, equals);\n        }\n\n        @Override\n        protected void putValue(Date value) {\n            put(VALUE, ObjectUtils.toISO8601DateOnly(value));\n        }\n    }\n}\n\n"
  },
  {
    "path": "cloudinary-core/src/main/java/com/cloudinary/metadata/Restrictions.java",
    "content": "package com.cloudinary.metadata;\n\nimport java.util.HashMap;\n\n/**\n * Represents the restrictions metadata field.\n */\npublic class Restrictions {\n\n    private final HashMap restrictions = new HashMap();\n\n    /**\n     * Set the custom field into restrictions.\n     * @param key The key of the field.\n     * @param value The value of the field.\n     */\n    public Restrictions setRestriction(String key, Object value) {\n        restrictions.put(key, value);\n        return this;\n    }\n\n    /**\n     * Set the read only ui field.\n     * @param value The read only ui value.\n     */\n    public Restrictions setReadOnlyUI(Boolean value) {\n        return setRestriction(\"readonly_ui\", value);\n    }\n\n    /**\n     * Set the read only ui field to true.\n     */\n    public Restrictions setReadOnlyUI() {\n        return this.setReadOnlyUI(true);\n    }\n\n    public HashMap toHash() {\n        return restrictions;\n    }\n}\n"
  },
  {
    "path": "cloudinary-core/src/main/java/com/cloudinary/metadata/SetMetadataField.java",
    "content": "package com.cloudinary.metadata;\n\nimport java.util.List;\n\n/**\n * Represents a metadata field with 'Set' type.\n */\npublic class SetMetadataField extends MetadataField<List<String>> {\n    public SetMetadataField() {\n        super(MetadataFieldType.SET);\n    }\n}\n"
  },
  {
    "path": "cloudinary-core/src/main/java/com/cloudinary/metadata/StringMetadataField.java",
    "content": "package com.cloudinary.metadata;\n\n/**\n * Represents a metadata field with 'String' type.\n */\npublic class StringMetadataField extends MetadataField<String> {\n    public StringMetadataField() {\n        super(MetadataFieldType.STRING);\n    }\n}"
  },
  {
    "path": "cloudinary-core/src/main/java/com/cloudinary/provisioning/Account.java",
    "content": "package com.cloudinary.provisioning;\n\nimport com.cloudinary.Api;\nimport com.cloudinary.Cloudinary;\nimport com.cloudinary.Util;\nimport com.cloudinary.api.ApiResponse;\nimport com.cloudinary.utils.Base64Coder;\nimport com.cloudinary.utils.ObjectUtils;\nimport com.cloudinary.utils.StringUtils;\n\nimport java.util.*;\n\n/**\n * Entry point class for all account and provisioning API actions: Manage users, cloud names and user groups.\n */\npublic class Account {\n    private static final String CLOUDINARY_ACCOUNT_URL = \"CLOUDINARY_ACCOUNT_URL\";\n    public static final String PROVISIONING = \"provisioning\";\n    public static final String ACCOUNTS = \"accounts\";\n    public static final String SUB_ACCOUNTS = \"sub_accounts\";\n    public static final String USERS = \"users\";\n    public static final String USER_GROUPS = \"user_groups\";\n    public static final String ACCESS_KEYS = \"access_keys\";\n\n    private final AccountConfiguration configuration;\n    private final String accountId;\n    private final String key;\n    private final String secret;\n    private final Api api;\n\n    /**\n     * Create a new instance to use the account API. The account information will be extracted from\n     * an environment variable CLOUDINARY_ACCOUNT_URL. If it's missing an exception will be thrown.\n     *\n     * @param cloudinary A cloudinary instance. This is used to fetch the correct network configuration.\n     */\n    public Account(Cloudinary cloudinary) {\n        String provisioningData = System.getProperty(CLOUDINARY_ACCOUNT_URL, System.getenv(CLOUDINARY_ACCOUNT_URL));\n        if (provisioningData != null) {\n            this.configuration = AccountConfiguration.from(provisioningData);\n            this.accountId = configuration.accountId;\n            this.key = configuration.provisioningApiKey;\n            this.secret = configuration.provisioningApiSecret;\n        } else {\n            throw new IllegalArgumentException(\"Must provide configuration instance or set an ENV variable: \" +\n                    \"CLOUDINARY_ACCOUNT_URL=account://provisioning_api_key:provisioning_api_secret@account_id\");\n        }\n\n        this.api = cloudinary.api();\n    }\n\n    /**\n     * Create a new instance to use the account API. The account information will be extracted from\n     *\n     * @param accountConfiguration Account configuration to use in requests.\n     * @param cloudinary           A cloudinary instance. This is used to fetch the correct network configuration.\n     */\n    public Account(AccountConfiguration accountConfiguration, Cloudinary cloudinary) {\n        this.configuration = accountConfiguration;\n        this.api = cloudinary.api();\n        this.accountId = accountConfiguration.accountId;\n        this.key = accountConfiguration.provisioningApiKey;\n        this.secret = accountConfiguration.provisioningApiSecret;\n    }\n\n    private ApiResponse callAccountApi(Api.HttpMethod method, List<String> uri, Map<String, Object> params, Map<String, Object> options) throws Exception {\n        options = verifyOptions(options);\n\n        if (options.containsKey(\"provisioning_api_key\")){\n            if (!options.containsKey(\"provisioning_api_secret\")){\n                throw new IllegalArgumentException(\"When providing key or secret through options, both must be provided\");\n            }\n        } else {\n            if (options.containsKey(\"provisioning_api_secret\")){\n                throw new IllegalArgumentException(\"When providing key or secret through options, both must be provided\");\n            }\n            options.put(\"provisioning_api_key\", key);\n            options.put(\"provisioning_api_secret\", secret);\n        }\n\n        Util.clearEmpty(params);\n\n        if (options == null) {\n            options = ObjectUtils.emptyMap();\n        }\n\n        String prefix = ObjectUtils.asString(options.get(\"upload_prefix\"), \"https://api.cloudinary.com\");\n        String apiKey = ObjectUtils.asString(options.get(\"provisioning_api_key\"));\n        if (apiKey == null) throw new IllegalArgumentException(\"Must supply provisioning_api_key\");\n        String apiSecret = ObjectUtils.asString(options.get(\"provisioning_api_secret\"));\n        if (apiSecret == null) throw new IllegalArgumentException(\"Must supply provisioning_api_secret\");\n\n        String apiUrl = StringUtils.join(Arrays.asList(prefix, \"v1_1\"), \"/\");\n        for (String component : uri) {\n            apiUrl = apiUrl + \"/\" + component;\n        }\n\n        String authorizationHeader = getAuthorizationHeaderValue(apiKey, apiSecret, null);\n\n        return api.getStrategy().callAccountApi(method, apiUrl, params, options, authorizationHeader);\n    }\n\n    /**\n     * A user role to use in the user management API (create/update user).\n     */\n    public enum Role {\n        MASTER_ADMIN(\"master_admin\"),\n        ADMIN(\"admin\"),\n        TECHNICAL_ADMIN(\"technical_admin\"),\n        BILLING(\"billing\"),\n        REPORTS(\"reports\"),\n        MEDIA_LIBRARY_ADMIN(\"media_library_admin\"),\n        MEDIA_LIBRARY_USER(\"media_library_user\");\n\n        private final String serializedValue;\n\n        Role(String serializedValue) {\n            this.serializedValue = serializedValue;\n        }\n\n        @Override\n        public String toString() {\n            return serializedValue;\n        }\n    }\n\n    // Sub accounts\n    /**\n     * Get details of a specific sub account\n     *\n     * @param subAccountId The id of the sub account\n     * @return the sub account details.\n     * @throws Exception If the request fails\n     */\n    public ApiResponse subAccount(String subAccountId) throws Exception {\n        return subAccount(subAccountId, Collections.<String, Object>emptyMap());\n    }\n\n    /**\n     * Get details of a specific sub account\n     *\n     * @param subAccountId The id of the sub account\n     * @param options      Generic advanced options map, see online documentation.\n     * @return the sub account details.\n     * @throws Exception If the request fails\n     */\n    public ApiResponse subAccount(String subAccountId, Map<String, Object> options) throws Exception {\n        List<String> uri = Arrays.asList(PROVISIONING, ACCOUNTS, accountId, \"sub_accounts\", subAccountId);\n        return callAccountApi(Api.HttpMethod.GET, uri, Collections.<String, Object>emptyMap(), options);\n    }\n\n    /**\n     * Get a list of sub accounts.\n     *\n     * @param enabled Optional. Whether to fetch enabled or disabled accounts. Default is all.\n     * @param ids     Optional. List of sub-account IDs. Up to 100. When provided, other filters are ignored.\n     * @param prefix  Optional. Search by prefix of the sub-account name. Case-insensitive.\n     * @return the list of sub-accounts details.\n     * @throws Exception If the request fails\n     */\n    public ApiResponse subAccounts(Boolean enabled, List<String> ids, String prefix) throws Exception {\n        return subAccounts(enabled, ids, prefix, Collections.<String, Object>emptyMap());\n    }\n\n    /**\n     * Get a list of sub accounts.\n     *\n     * @param enabled Optional. Whether to fetch enabled or disabled accounts. Default is all.\n     * @param ids     Optional. List of sub-account IDs. Up to 100. When provided, other filters are ignored.\n     * @param prefix  Optional. Search by prefix of the sub-account name. Case-insensitive.\n     * @param options Generic advanced options map, see online documentation.\n     * @return the list of sub-accounts details.\n     * @throws Exception If the request fails\n     */\n    public ApiResponse subAccounts(Boolean enabled, List<String> ids, String prefix, Map<String, Object> options) throws Exception {\n        List<String> uri = Arrays.asList(PROVISIONING, ACCOUNTS, accountId, \"sub_accounts\");\n        return callAccountApi(Api.HttpMethod.GET, uri,\n                ObjectUtils.asMap(\"accountId\", accountId, \"enabled\", enabled, \"ids\", ids, \"prefix\", prefix), options);\n    }\n\n    /**\n     * @param name             Required. The name displayed in the management console.\n     * @param cloudName        Optional, unique (case insensitive)\n     * @param customAttributes Custom attributes associated with the sub-account, as a map of key/value pairs.\n     * @param enabled          Optional. Whether to create the account as enabled (default is enabled).\n     * @param baseAccount      Optional. ID of sub-account from which to copy settings\n     * @return details of the created sub-account\n     * @throws Exception If the request fails\n     */\n    public ApiResponse createSubAccount(String name, String cloudName, Map customAttributes, boolean enabled, String baseAccount) throws Exception {\n        return createSubAccount(name, cloudName, customAttributes, enabled, baseAccount, Collections.<String, Object>emptyMap());\n    }\n\n    /**\n     * @param name             Required. The name displayed in the management console.\n     * @param cloudName        Optional, unique (case insensitive)\n     * @param customAttributes Custom attributes associated with the sub-account, as a map of key/value pairs.\n     * @param enabled          Optional. Whether to create the account as enabled (default is enabled).\n     * @param baseAccount      Optional. ID of sub-account from which to copy settings\n     * @param options          Generic advanced options map, see online documentation.\n     * @return details of the created sub-account\n     * @throws Exception If the request fails\n     */\n    public ApiResponse createSubAccount(String name, String cloudName, Map customAttributes, boolean enabled, String baseAccount, Map<String, Object> options) throws Exception {\n        options = verifyOptions(options);\n        options.put(\"content_type\", \"json\");\n\n        List<String> uri = Arrays.asList(PROVISIONING, ACCOUNTS, accountId, \"sub_accounts\");\n\n        return callAccountApi(Api.HttpMethod.POST, uri, ObjectUtils.asMap(\n                \"cloud_name\", cloudName,\n                \"name\", name,\n                \"custom_attributes\", customAttributes,\n                \"enabled\", enabled,\n                \"base_sub_account_id\", baseAccount),\n                options);\n    }\n\n    /**\n     * @param subAccountId     The id of the sub-account to update\n     * @param name             The name displayed in the management console.\n     * @param cloudName        The cloud name to set.\n     * @param customAttributes ACustom attributes associated with the sub-account, as a map of key/value pairs.\n     * @param enabled          Set the sub-account as enabled or not.\n     * @return details of the updated sub-account\n     * @throws Exception If the request fails\n     */\n    public ApiResponse updateSubAccount(String subAccountId, String name, String cloudName, Map<String, String> customAttributes, Boolean enabled) throws Exception {\n        return updateSubAccount(subAccountId, name, cloudName, customAttributes, enabled, Collections.<String, Object>emptyMap());\n    }\n\n    /**\n     * @param subAccountId     The id of the sub-account to update\n     * @param name             The name displayed in the management console.\n     * @param cloudName        The cloud name to set.\n     * @param customAttributes ACustom attributes associated with the sub-account, as a map of key/value pairs.\n     * @param enabled          Set the sub-account as enabled or not.\n     * @param options          Generic advanced options map, see online documentation.\n     * @return details of the updated sub-account\n     * @throws Exception If the request fails\n     */\n    public ApiResponse updateSubAccount(String subAccountId, String name, String cloudName, Map<String, String> customAttributes, Boolean enabled, Map<String, Object> options) throws Exception {\n        options = verifyOptions(options);\n        options.put(\"content_type\", \"json\");\n\n        List<String> uri = Arrays.asList(PROVISIONING, ACCOUNTS, accountId, \"sub_accounts\", subAccountId);\n\n        return callAccountApi(Api.HttpMethod.PUT, uri, ObjectUtils.asMap(\n                \"cloud_name\", cloudName,\n                \"name\", name,\n                \"custom_attributes\", customAttributes,\n                \"enabled\", enabled),\n                options);\n    }\n\n    /**\n     * Deletes the sub-account.\n     *\n     * @param subAccountId The id of the sub-account to delete\n     * @return result message.\n     * @throws Exception If the request fails.\n     */\n    public ApiResponse deleteSubAccount(String subAccountId) throws Exception {\n        return deleteSubAccount(subAccountId, Collections.<String, Object>emptyMap());\n    }\n\n    /**\n     * Deletes the sub-account.\n     *\n     * @param subAccountId The id of the sub-account to delete\n     * @param options      Generic advanced options map, see online documentation.\n     * @return result message.\n     * @throws Exception If the request fails.\n     */\n    public ApiResponse deleteSubAccount(String subAccountId, Map<String, Object> options) throws Exception {\n        List<String> uri = Arrays.asList(PROVISIONING, ACCOUNTS, accountId, \"sub_accounts\", subAccountId);\n        return callAccountApi(Api.HttpMethod.DELETE, uri, Collections.<String, Object>emptyMap(), options);\n    }\n\n    // Users\n    /**\n     * Get details of a specific user.\n     *\n     * @param userId  The id of the user to fetch\n     * @return details of the user.\n     * @throws Exception If the request fails.\n     */\n    public ApiResponse user(String userId) throws Exception {\n        return user(userId,null);\n    }\n\n    /**\n     * Get details of a specific user.\n     *\n     * @param userId  The id of the user to fetch\n     * @param options Generic advanced options map, see online documentation.\n     * @return details of the user.\n     * @throws Exception If the request fails.\n     */\n    public ApiResponse user(String userId, Map<String, Object> options) throws Exception {\n        List<String> uri = Arrays.asList(PROVISIONING, ACCOUNTS, accountId, USERS, userId);\n        return callAccountApi(Api.HttpMethod.GET, uri, Collections.<String, Object>emptyMap(), options);\n    }\n\n    /**\n     * Get a list of the users according to filters.\n     *\n     * @param pending      Optional. Limit results to pending users (true), users that are not pending (false), or all users (null)\n     * @param userIds      Optionals. List of user IDs. Up to 100\n     * @param prefix       Optional. Search by prefix of the user's name or email. Case-insensitive\n     * @param subAccountId Optional. Return only users who have access to the given sub-account\n     * @return the users' details.\n     * @throws Exception If the request fails.\n     */\n    public ApiResponse users(Boolean pending, List<String> userIds, String prefix, String subAccountId) throws Exception {\n        return users(pending, userIds, prefix, subAccountId,null);\n    }\n\n    /**\n     * Get a list of the users according to filters.\n     *\n     * @param pending      Optional. Limit results to pending users (true), users that are not pending (false), or all users (null)\n     * @param userIds      Optionals. List of user IDs. Up to 100\n     * @param prefix       Optional. Search by prefix of the user's name or email. Case-insensitive\n     * @param subAccountId Optional. Return only users who have access to the given sub-account\n     * @param options      Generic advanced options map, see online documentation.\n     * @return the users' details.\n     * @throws Exception If the request fails.\n     */\n    public ApiResponse users(Boolean pending, List<String> userIds, String prefix, String subAccountId, Map<String, Object> options) throws Exception {\n        List<String> uri = Arrays.asList(PROVISIONING, ACCOUNTS, accountId, USERS);\n        return callAccountApi(Api.HttpMethod.GET, uri,\n                ObjectUtils.asMap(\"accountId\", accountId,\n                        \"pending\", pending,\n                        \"ids\", userIds,\n                        \"prefix\", prefix,\n                        \"sub_account_id\", subAccountId), options);\n    }\n\n    /**\n     * Create a new user.\n     *\n     * @param name           Required. Username.\n     * @param email          Required. User's email.\n     * @param role           Required. User's role.\n     * @param subAccountsIds Optional. Sub-accounts for which the user should have access.\n     *                       If not provided or empty, user should have access to all accounts.\n     * @return The newly created user details.\n     * @throws Exception If the request fails.\n     */\n    public ApiResponse createUser(String name, String email, Role role, List<String> subAccountsIds) throws Exception {\n        return createUser(name, email, role, subAccountsIds, null);\n    }\n\n    /**\n     * Create a new user.\n     *\n     * @param name           Required. Username.\n     * @param email          Required. User's email.\n     * @param role           Required. User's role.\n     * @param subAccountsIds Optional. Sub-accounts for which the user should have access.\n     *                       If not provided or empty, user should have access to all accounts.\n     * @param options        Generic advanced options map, see online documentation.\n     * @return The newly created user details.\n     * @throws Exception If the request fails.\n     */\n    public ApiResponse createUser(String name, String email, Role role, List<String> subAccountsIds, Map<String, Object> options) throws Exception {\n        return createUser(name, email, role, null, subAccountsIds, options);\n    }\n\n    /**\n     * Create a new user.\n     *\n     * @param name           Required. Username.\n     * @param email          Required. User's email.\n     * @param role           Required. User's role.\n     * @param enabled        Optional. User's status (enabled or disabled).\n     * @param subAccountsIds Optional. Sub-accounts for which the user should have access.\n     *                       If not provided or empty, user should have access to all accounts.\n     * @param options        Generic advanced options map, see online documentation.\n     * @return The newly created user details.\n     * @throws Exception If the request fails.\n     */\n    public ApiResponse createUser(String name, String email, Role role, Boolean enabled, List<String> subAccountsIds, Map<String, Object> options) throws Exception {\n        List<String> uri = Arrays.asList(PROVISIONING, ACCOUNTS, accountId, USERS);\n        return performUserAction(Api.HttpMethod.POST, uri, email, name, role, enabled, subAccountsIds, options);\n    }\n\n    /**\n     * Update an existing user.\n     *\n     * @param userId         The id of the user to update.\n     * @param name           Username.\n     * @param email          User's email.\n     * @param role           User's role.\n     * @param subAccountsIds Sub-accounts for which the user should have access.\n     *                       If not provided or empty, user should have access to all accounts.\n     * @return The updated user details\n     * @throws Exception If the request fails.\n     */\n    public ApiResponse updateUser(String userId, String name, String email, Role role, List<String> subAccountsIds) throws Exception {\n        return updateUser(userId, name, email, role, subAccountsIds,null);\n    }\n\n    /**\n     * Update an existing user.\n     *\n     * @param userId         The id of the user to update.\n     * @param name           Username.\n     * @param email          User's email.\n     * @param role           User's role.\n     * @param subAccountsIds Sub-accounts for which the user should have access.\n     *                       If not provided or empty, user should have access to all accounts.\n     * @param options        Generic advanced options map, see online documentation.\n     * @return The updated user details\n     * @throws Exception If the request fails.\n     */\n    public ApiResponse updateUser(String userId, String name, String email, Role role, List<String> subAccountsIds, Map<String, Object> options) throws Exception {\n        return updateUser(userId, name ,email ,role ,null , subAccountsIds , options);\n    }\n\n    /**\n     * Update an existing user.\n     *\n     * @param userId         The id of the user to update.\n     * @param name           Username.\n     * @param email          User's email.\n     * @param role           User's role.\n     * @param enabled        User's status (enabled or disabled)\n     * @param subAccountsIds Sub-accounts for which the user should have access.\n     *                       If not provided or empty, user should have access to all accounts.\n     * @param options        Generic advanced options map, see online documentation.\n     * @return The updated user details\n     * @throws Exception If the request fails.\n     */\n    public ApiResponse updateUser(String userId, String name, String email, Role role, Boolean enabled, List<String> subAccountsIds, Map<String, Object> options) throws Exception {\n        List<String> uri = Arrays.asList(PROVISIONING, ACCOUNTS, accountId, USERS, userId);\n        return performUserAction(Api.HttpMethod.PUT, uri, email, name, role, enabled, subAccountsIds, options);\n    }\n\n    /**\n     * Delete a user.\n     *\n     * @param userId  Id of the user to delete.\n     * @return result message.\n     * @throws Exception\n     */\n    public ApiResponse deleteUser(String userId) throws Exception {\n        return deleteUser(userId,null);\n    }\n\n    /**\n     * Delete a user.\n     *\n     * @param userId  Id of the user to delete.\n     * @param options Generic advanced options map, see online documentation.\n     * @return result message.\n     * @throws Exception\n     */\n    public ApiResponse deleteUser(String userId, Map<String, Object> options) throws Exception {\n        List<String> uri = Arrays.asList(PROVISIONING, ACCOUNTS, accountId, USERS, userId);\n        return callAccountApi(Api.HttpMethod.DELETE, uri, Collections.<String, Object>emptyMap(), options);\n    }\n\n    // Groups\n    /**\n     * Create a new user group\n     * @param name Required. Name for the group.\n     * @return The newly created group.\n     * @throws Exception If the request fails\n     */\n    public ApiResponse createUserGroup(String name) throws Exception {\n        return createUserGroup(name,null);\n    }\n\n    /**\n     * Create a new user group\n     * @param name Required. Name for the group.\n     * @param options Generic advanced options map, see online documentation.\n     * @return The newly created group.\n     * @throws Exception If the request fails\n     */\n    public ApiResponse createUserGroup(String name, Map<String, Object> options) throws Exception {\n        List<String> uri = Arrays.asList(PROVISIONING, ACCOUNTS, accountId, USER_GROUPS);\n        return callAccountApi(Api.HttpMethod.POST, uri, ObjectUtils.asMap(\"name\", name), options);\n    }\n\n    /**\n     * Update an existing user group\n     *\n     * @param groupId The id of the group to update\n     * @param name The name of the group.\n     * @return The updated group.\n     * @throws Exception If the request fails\n     */\n    public ApiResponse updateUserGroup(String groupId, String name) throws Exception {\n        return updateUserGroup(groupId, name,null);\n    }\n\n    /**\n     * Update an existing user group\n     *\n     * @param groupId The id of the group to update\n     * @param name The name of the group.\n     * @param options Generic advanced options map, see online documentation.\n     * @return The updated group.\n     * @throws Exception If the request fails\n     */\n    public ApiResponse updateUserGroup(String groupId, String name, Map<String, Object> options) throws Exception {\n        List<String> uri = Arrays.asList(PROVISIONING, ACCOUNTS, accountId, USER_GROUPS, groupId);\n        return callAccountApi(Api.HttpMethod.PUT, uri, ObjectUtils.asMap(\"name\", name), options);\n    }\n\n    /**\n     * Delete a user group\n     *\n     * @param groupId The group id to delete\n     * @return A result message.\n     * @throws Exception if the request fails.\n     */\n    public ApiResponse deleteUserGroup(String groupId) throws Exception {\n        return deleteUserGroup(groupId,null);\n    }\n\n    /**\n     * Delete a user group\n     *\n     * @param groupId The group id to delete\n     * @param options Generic advanced options map, see online documentation.\n     * @return A result message.\n     * @throws Exception if the request fails.\n     */\n    public ApiResponse deleteUserGroup(String groupId, Map<String, Object> options) throws Exception {\n        List<String> uri = Arrays.asList(PROVISIONING, ACCOUNTS, accountId, USER_GROUPS, groupId);\n        return callAccountApi(Api.HttpMethod.DELETE, uri, Collections.<String, Object>emptyMap(), options);\n    }\n\n    /**\n     * Add an existing user to a group.\n     * @param groupId The group id.\n     * @param userId The user id to add.\n     * @throws Exception If the request fails\n     */\n    public ApiResponse addUserToGroup(String groupId, String userId) throws Exception {\n        return addUserToGroup(groupId, userId,null);\n    }\n    /**\n     * Add an existing user to a group.\n     * @param groupId The group id.\n     * @param userId The user id to add.\n     * @param options Generic advanced options map, see online documentation.\n     * @throws Exception If the request fails\n     */\n    public ApiResponse addUserToGroup(String groupId, String userId, Map<String, Object> options) throws Exception {\n        List<String> uri = Arrays.asList(PROVISIONING, ACCOUNTS, accountId, USER_GROUPS, groupId, USERS, userId);\n        return callAccountApi(Api.HttpMethod.POST, uri, Collections.<String, Object>emptyMap(), options);\n    }\n\n    /**\n     * Removes a user from a group.\n     * @param groupId The group id.\n     * @param userId The id of the user to remove\n     * @return A result message\n     * @throws Exception If the request fails.\n     */\n    public ApiResponse removeUserFromGroup(String groupId, String userId) throws Exception {\n        return removeUserFromGroup(groupId, userId,null);\n    }\n    /**\n     * Removes a user from a group.\n     * @param groupId The group id.\n     * @param userId The id of the user to remove\n     * @param options Generic advanced options map, see online documentation.\n     * @return A result message\n     * @throws Exception If the request fails.\n     */\n    public ApiResponse removeUserFromGroup(String groupId, String userId, Map<String, Object> options) throws Exception {\n        List<String> uri = Arrays.asList(PROVISIONING, ACCOUNTS, accountId, USER_GROUPS, groupId, USERS, userId);\n        return callAccountApi(Api.HttpMethod.DELETE, uri, Collections.<String, Object>emptyMap(), options);\n    }\n\n    /**\n     * Get details of a group.\n     * @param groupId The group id to fetch\n     * @return Details of the group.\n     * @throws Exception If the request fails.\n     */\n    public ApiResponse userGroup(String groupId) throws Exception {\n        return userGroup(groupId,null);\n    }\n\n    /**\n     * Get details of a group.\n     * @param groupId The group id to fetch\n     * @param options Generic advanced options map, see online documentation.\n     * @return Details of the group.\n     * @throws Exception If the request fails.\n     */\n    public ApiResponse userGroup(String groupId, Map<String, Object> options) throws Exception {\n        List<String> uri = Arrays.asList(PROVISIONING, ACCOUNTS, accountId, USER_GROUPS, groupId);\n        return callAccountApi(Api.HttpMethod.GET, uri, Collections.<String, Object>emptyMap(), options);\n    }\n\n    /**\n     * Gets a list of all the user groups.\n     * @return The list of the groups.\n     * @throws Exception If the request fails.\n     */\n    public ApiResponse userGroups() throws Exception {\n        return userGroups(Collections.<String, Object>emptyMap());\n    }\n\n    /**\n     * Gets a list of all the user groups.\n     * @param options Generic advanced options map, see online documentation.\n     * @return The list of the groups.\n     * @throws Exception If the request fails.\n     */\n    public ApiResponse userGroups(Map<String, Object> options) throws Exception {\n        List<String> uri = Arrays.asList(PROVISIONING, ACCOUNTS, accountId, USER_GROUPS);\n        return callAccountApi(Api.HttpMethod.GET, uri, Collections.<String, Object>emptyMap(), options);\n    }\n\n    /**\n     * Lists the users belonging to this user group.\n     * @param groupId The id of the user group.\n     * @return The list of users in that group.\n     * @throws Exception If the request fails.\n     */\n    public ApiResponse userGroupUsers(String groupId) throws Exception {\n        return userGroupUsers(groupId,null);\n    }\n    /**\n     * Lists the users belonging to this user group.\n     * @param groupId The id of the user group.\n     * @param options Generic advanced options map, see online documentation.\n     * @return The list of users in that group.\n     * @throws Exception If the request fails.\n     */\n    public ApiResponse userGroupUsers(String groupId, Map<String, Object> options) throws Exception {\n        List<String> uri = Arrays.asList(PROVISIONING, ACCOUNTS, accountId, USER_GROUPS, groupId, USERS);\n        return callAccountApi(Api.HttpMethod.GET, uri, Collections.<String, Object>emptyMap(), options);\n    }\n\n    /**\n     * Lists the access keys belonging to this sub account id.\n     * @param subAccountId The id of the user group.\n     * @param options Generic advanced options map, see online documentation.\n     * @return The list of access keys in that sub account id.\n     * @throws Exception If the request fails.\n     */\n    public ApiResponse getAccessKeys(String subAccountId, Map<String, Object> options) throws Exception {\n        List<String> uri = Arrays.asList(PROVISIONING, ACCOUNTS, accountId, SUB_ACCOUNTS, subAccountId);\n        return callAccountApi(Api.HttpMethod.GET, uri, Collections.<String, Object>emptyMap(), options);\n    }\n\n    /**\n     * Creates a new access key for this sub account id.\n     * @param subAccountId The id of the user group.\n     * @param name The name for the access key.\n     * @param enabled Access key's status (enabled or disabled).\n     * @param options Generic advanced options map, see online documentation.\n     * @return The created access key.\n     * @throws Exception If the request fails.\n     */\n    public ApiResponse createAccessKey(String subAccountId, String name, Boolean enabled, Map<String, Object> options)  throws Exception {\n        List<String> uri = Arrays.asList(PROVISIONING, ACCOUNTS, accountId, SUB_ACCOUNTS, subAccountId, ACCESS_KEYS);\n        return callAccountApi(Api.HttpMethod.POST, uri, ObjectUtils.asMap(\"name\", name, \"enabled\", enabled), options);\n    }\n\n    /**\n     * Updates an existing access key for this sub account id.\n     * @param subAccountId The id of the user group.\n     * @param accessKey The key of the access key.\n     * @param name The name for the access key.\n     * @param enabled Access key's status (enabled or disabled).\n     * @param options Generic advanced options map, see online documentation.\n     * @return The updated access key.\n     * @throws Exception If the request fails.\n     */\n    public ApiResponse updateAccessKey(String subAccountId, String accessKey, String name, Boolean enabled, Map<String, Object> options) throws Exception {\n        List<String> uri = Arrays.asList(PROVISIONING, ACCOUNTS, accountId, SUB_ACCOUNTS, subAccountId, ACCESS_KEYS, accessKey);\n        return callAccountApi(Api.HttpMethod.PUT, uri, ObjectUtils.asMap(\"name\", name, \"enabled\", enabled), options);\n    }\n\n    /**\n     * Deletes an existing access key for this sub account id.\n     * @param subAccountId The id of the user group.\n     * @param accessKey The key of the access key.\n     * @param options Generic advanced options map, see online documentation.\n     * @return \"message\": \"ok\".\n     * @throws Exception If the request fails.\n     */\n    public ApiResponse deleteAccessKey(String subAccountId, String accessKey, Map<String, Object> options) throws Exception {\n        List<String> uri = Arrays.asList(PROVISIONING, ACCOUNTS, accountId, SUB_ACCOUNTS, subAccountId, ACCESS_KEYS, accessKey);\n        return callAccountApi(Api.HttpMethod.DELETE, uri, Collections.<String, Object>emptyMap(), options);\n    }\n\n    /**\n     * Private helper method for users api calls\n     * @param method Http method\n     * @param uri Uri to call\n     * @param email user email\n     * @param name user name\n     * @param role user role\n     * @param subAccountsIds suv accounts ids the user has access to.\n     * @param options\n     * @return The response of the api call.\n     * @throws Exception If the request fails.\n     */\n    private ApiResponse performUserAction(Api.HttpMethod method, List<String> uri, String email, String name, Role role, Boolean enabled, List<String> subAccountsIds, Map<String, Object> options) throws Exception {\n        options = verifyOptions(options);\n        options.put(\"content_type\", \"json\");\n\n        return callAccountApi(method, uri, ObjectUtils.asMap(\n                \"email\", email,\n                \"name\", name,\n                \"role\", role == null ? null : role.serializedValue,\n                \"enabled\", enabled,\n                \"sub_account_ids\", subAccountsIds),\n                options);\n    }\n\n    private Map<String, Object> verifyOptions(Map<String, Object> options) {\n        if (options == null || options == Collections.EMPTY_MAP) {\n            return new HashMap<String, Object>(2); // Two, since api key and secret will be populated later\n        }\n\n        return options;\n    }\n\n    protected String getAuthorizationHeaderValue(String apiKey, String apiSecret, String oauthToken) {\n        if (oauthToken != null){\n            return \"Bearer \" + oauthToken;\n        } else {\n            return \"Basic \" + Base64Coder.encodeString(apiKey + \":\" + apiSecret);\n        }\n    }\n}\n"
  },
  {
    "path": "cloudinary-core/src/main/java/com/cloudinary/provisioning/AccountConfiguration.java",
    "content": "package com.cloudinary.provisioning;\n\nimport com.cloudinary.utils.StringUtils;\n\nimport java.net.URI;\n\npublic class AccountConfiguration {\n    private static final String SEPARATOR = \":\";\n    String accountId;\n    String provisioningApiKey;\n    String provisioningApiSecret;\n\n    public AccountConfiguration(String accountId, String provisioningApiKey, String provisioningApiSecret) {\n        this.accountId = accountId;\n        this.provisioningApiKey = provisioningApiKey;\n        this.provisioningApiSecret = provisioningApiSecret;\n    }\n\n    public static AccountConfiguration from(String accountUrl) {\n        URI uri = URI.create(accountUrl);\n\n        String accountId = uri.getHost();\n        if (StringUtils.isBlank(accountId)) throw new IllegalArgumentException(\"Account id must be provided in account url\");\n\n        if (uri.getUserInfo() == null) throw new IllegalArgumentException(\"Full credentials (key+secret) must be provided in account url\");\n        String[] credentials = uri.getUserInfo().split(\":\");\n        if (credentials.length < 2 ||\n                StringUtils.isBlank(credentials[0]) ||\n                StringUtils.isBlank(credentials[1])) {\n            throw new IllegalArgumentException(\"Full credentials (key+secret) must be provided in account url\");\n        }\n\n        return new AccountConfiguration(accountId, credentials[0], credentials[1]);\n    }\n}\n"
  },
  {
    "path": "cloudinary-core/src/main/java/com/cloudinary/strategies/AbstractApiStrategy.java",
    "content": "package com.cloudinary.strategies;\n\nimport com.cloudinary.Api;\nimport com.cloudinary.Api.HttpMethod;\nimport com.cloudinary.api.ApiResponse;\nimport java.util.Map;\n\n\npublic abstract class AbstractApiStrategy {\n    protected Api api;\n\n    public void init(Api api) {\n        this.api = api;\n    }\n\n    @SuppressWarnings(\"rawtypes\")\n    public abstract ApiResponse callApi(HttpMethod method, String apiUrl, Map<String, ? extends Object> params, Map options, String authorizationHeader) throws Exception;\n\n    public abstract ApiResponse callAccountApi(HttpMethod method, String apiUrl, Map<String, ? extends Object> params, Map options, String authorizationHeader) throws Exception;\n}\n"
  },
  {
    "path": "cloudinary-core/src/main/java/com/cloudinary/strategies/AbstractUploaderStrategy.java",
    "content": "package com.cloudinary.strategies;\n\nimport com.cloudinary.Cloudinary;\nimport com.cloudinary.ProgressCallback;\nimport com.cloudinary.Uploader;\nimport com.cloudinary.utils.ObjectUtils;\nimport com.cloudinary.utils.StringUtils;\nimport org.cloudinary.json.JSONException;\nimport org.cloudinary.json.JSONObject;\n\nimport java.io.IOException;\nimport java.util.Arrays;\nimport java.util.HashMap;\nimport java.util.Map;\n\npublic abstract class AbstractUploaderStrategy {\n    private final static int[] ERROR_CODES = new int[]{400, 401, 403, 404, 420, 500};\n    protected Uploader uploader;\n\n    public void init(Uploader uploader) {\n        this.uploader = uploader;\n    }\n\n    public Cloudinary cloudinary() {\n        return this.uploader.cloudinary();\n    }\n\n    @SuppressWarnings(\"rawtypes\")\n    public Map callApi(String action, Map<String, Object> params, Map options, Object file) throws IOException {\n        return callApi(action, params, options, file, null);\n    }\n\n    public abstract Map callApi(String action, Map<String, Object> params, Map options, Object file, ProgressCallback progressCallback) throws IOException;\n\n    protected String buildUploadUrl(String action, Map options) {\n        String cloudinary = ObjectUtils.asString(options.get(\"upload_prefix\"),\n                ObjectUtils.asString(uploader.cloudinary().config.uploadPrefix, \"https://api.cloudinary.com\"));\n        String cloud_name = ObjectUtils.asString(options.get(\"cloud_name\"), ObjectUtils.asString(uploader.cloudinary().config.cloudName));\n        if (cloud_name == null)\n            throw new IllegalArgumentException(\"Must supply cloud_name in tag or in configuration\");\n\n        if (action.equals(\"delete_by_token\")) {\n            // delete_by_token doesn't need resource_type\n            return StringUtils.join(new String[]{cloudinary, \"v1_1\", cloud_name, action}, \"/\");\n        } else {\n            String resource_type = ObjectUtils.asString(options.get(\"resource_type\"), \"image\");\n            return StringUtils.join(new String[]{cloudinary, \"v1_1\", cloud_name, resource_type, action}, \"/\");\n        }\n    }\n\n    protected Map processResponse(boolean returnError, int code, String responseData) {\n        String errorMessage = null;\n        Map result = null;\n        if (code == 200 || includesServerResponse(code)) {\n            try {\n                JSONObject responseJSON = new JSONObject(responseData);\n                result = ObjectUtils.toMap(responseJSON);\n\n                if (result.containsKey(\"error\")) {\n                    Map error = (Map) result.get(\"error\");\n                    error.put(\"http_code\", code);\n                    errorMessage = (String) error.get(\"message\");\n                }\n            } catch (JSONException e) {\n                errorMessage = \"Invalid JSON response from server \" + e.getMessage();\n            }\n        } else {\n            errorMessage = \"Server returned unexpected status code - \" + code;\n            if (StringUtils.isNotBlank(responseData)) {\n                errorMessage += (\" - \" + responseData);\n            }\n        }\n\n        if (StringUtils.isNotBlank(errorMessage)) {\n            if (returnError) {\n                // return a result containing the error instead of throwing an exception:\n                if (result == null) {\n                    Map error = new HashMap();\n                    error.put(\"http_code\", code);\n                    error.put(\"message\", errorMessage);\n                    result = new HashMap();\n                    result.put(\"error\", error);\n                } // else - Result is already built, with the error inside. Nothing to do.\n            } else {\n                throw new RuntimeException(errorMessage);\n            }\n        }\n\n        return result;\n    }\n\n    private boolean includesServerResponse(int code) {\n        return Arrays.binarySearch(ERROR_CODES, code) >= 0;\n    }\n\n    protected boolean requiresSigning(String action, Map options) {\n        boolean unsigned = Boolean.TRUE.equals(options.get(\"unsigned\"));\n        boolean deleteByToken = \"delete_by_token\".equals(action);\n\n        return !unsigned && !deleteByToken;\n    }\n}"
  },
  {
    "path": "cloudinary-core/src/main/java/com/cloudinary/strategies/StrategyLoader.java",
    "content": "package com.cloudinary.strategies;\n\nimport java.util.List;\n\npublic class StrategyLoader {\n\n    @SuppressWarnings(\"unchecked\")\n    public static <T> T load(String className) {\n        T result = null;\n        try {\n            Class<?> clazz = Class.forName(className);\n            result = (T) clazz.newInstance();\n        } catch (Exception e) {\n        }\n        return result;\n    }\n\n    public static <T> T find(List<String> strategies) {\n        for (int i = 0; i < strategies.size(); i++) {\n            T strategy = load(strategies.get(i));\n            if (strategy != null) {\n                return strategy;\n            }\n        }\n        return null;\n\n    }\n\n    public boolean exists(List<String> strategies) {\n        return find(strategies) != null;\n    }\n\n}\n"
  },
  {
    "path": "cloudinary-core/src/main/java/com/cloudinary/transformation/AbstractLayer.java",
    "content": "package com.cloudinary.transformation;\n\nimport java.io.Serializable;\nimport java.util.ArrayList;\n\nimport com.cloudinary.utils.StringUtils;\n\npublic abstract class AbstractLayer<T extends AbstractLayer<T>> implements Serializable{\n    abstract T getThis();\n\n    protected String resourceType = null;\n    protected String type = null;\n    protected String publicId = null;\n    protected String format = null;\n\n    public T resourceType(String resourceType) {\n        this.resourceType = resourceType;\n        return getThis();\n    }\n\n    public T type(String type) {\n        this.type = type;\n        return getThis();\n    }\n\n    public T publicId(String publicId) {\n        this.publicId = publicId.replace('/', ':');\n        return getThis();\n    }\n\n    public T format(String format) {\n        this.format = format;\n        return getThis();\n    }\n\n    @Override\n    public String toString() {\n        ArrayList<String> components = new ArrayList<String>();\n\n        if (this.resourceType != null && !this.resourceType.equals(\"image\")) {\n            components.add(this.resourceType);\n        }\n\n        if (this.type != null && !this.type.equals(\"upload\")) {\n            components.add(this.type);\n        }\n\n        if (this.publicId == null) {\n            throw new IllegalArgumentException(\"Must supply publicId\");\n        }\n\n        components.add(formattedPublicId());\n\n        return StringUtils.join(components, \":\");\n    }\n\n    protected String formattedPublicId() {\n        String transientPublicId = this.publicId;\n\n        if (this.format != null) {\n            transientPublicId = transientPublicId + \".\" + this.format;\n        }\n\n        return transientPublicId;\n    }\n}\n"
  },
  {
    "path": "cloudinary-core/src/main/java/com/cloudinary/transformation/BaseExpression.java",
    "content": "package com.cloudinary.transformation;\n\nimport com.cloudinary.Transformation;\nimport com.cloudinary.utils.ObjectUtils;\nimport com.cloudinary.utils.StringUtils;\n\nimport java.util.*;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\n/**\n * Defines an expression used in transformation parameter values\n *\n * @param <T> Children must define themselves as T\n */\npublic abstract class BaseExpression<T extends BaseExpression> {\n    public static final Map<String, String> OPERATORS = ObjectUtils.asMap(\n            \"=\", \"eq\",\n            \"!=\", \"ne\",\n            \"<\", \"lt\",\n            \">\", \"gt\",\n            \"<=\", \"lte\",\n            \">=\", \"gte\",\n            \"&&\", \"and\",\n            \"||\", \"or\",\n            \"*\", \"mul\",\n            \"/\", \"div\",\n            \"+\", \"add\",\n            \"-\", \"sub\",\n            \"^\", \"pow\"\n    );\n    public static final Map<String, String> PREDEFINED_VARS = ObjectUtils.asMap(\n            \"width\", \"w\",\n            \"height\", \"h\",\n            \"initialWidth\", \"iw\",\n            \"initialHeight\", \"ih\",\n            \"aspect_ratio\", \"ar\",\n            \"initial_aspect_ratio\", \"iar\",\n            \"aspectRatio\", \"ar\",\n            \"initialAspectRatio\", \"iar\",\n            \"page_count\", \"pc\",\n            \"pageCount\", \"pc\",\n            \"face_count\", \"fc\",\n            \"faceCount\", \"fc\",\n            \"current_page\", \"cp\",\n            \"currentPage\", \"cp\",\n            \"tags\", \"tags\",\n            \"pageX\", \"px\",\n            \"pageY\", \"py\",\n            \"duration\",\"du\",\n            \"initial_duration\",\"idu\",\n            \"initialDuration\",\"idu\"\n\n    );\n    private static final Pattern PATTERN = getPattern();\n    private static final Pattern USER_VARIABLE_PATTERN = Pattern.compile(\"\\\\$_*[^_]+\");\n\n    protected List<String> expressions = null;\n    protected Transformation parent = null;\n\n    protected BaseExpression() {\n        expressions = new ArrayList<String>();\n    }\n\n    /**\n     * Normalize an expression string, replace \"nice names\" with their coded values and spaces with \"_\".\n     *\n     * @param expression an expression\n     * @return a parsed expression\n     */\n    public static String normalize(Object expression) {\n        if (expression == null) {\n            return null;\n        }\n\n        // If it's a number it's not an expression\n        if (expression instanceof Number){\n            return String.valueOf(expression);\n        }\n\n        String conditionStr = StringUtils.mergeToSingleUnderscore(String.valueOf(expression));\n\n        Matcher m = USER_VARIABLE_PATTERN.matcher(conditionStr);\n        StringBuilder builder = new StringBuilder();\n        int lastMatchEnd = 0;\n        while (m.find()) {\n            String beforeMatch = conditionStr.substring(lastMatchEnd, m.start());\n            builder.append(normalizeBuiltins(beforeMatch));\n            builder.append(m.group());\n            lastMatchEnd = m.end();\n        }\n        builder.append(normalizeBuiltins(conditionStr.substring(lastMatchEnd)));\n        return builder.toString();\n    }\n\n    private static String normalizeBuiltins(String input) {\n        String replacement;\n        Matcher matcher = PATTERN.matcher(input);\n        StringBuffer result = new StringBuffer(input.length());\n        while (matcher.find()) {\n            if (OPERATORS.containsKey(matcher.group())) {\n                replacement = (String) OPERATORS.get(matcher.group());\n            } else if (PREDEFINED_VARS.containsKey(matcher.group())) {\n                replacement = (String) PREDEFINED_VARS.get(matcher.group());\n            } else {\n                replacement = matcher.group();\n            }\n            matcher.appendReplacement(result, replacement);\n        }\n        matcher.appendTail(result);\n        return result.toString();\n    }\n\n    /**\n     * @return a regex pattern for operators and predefined vars as /((operators)(?=[ _])|variables)/\n     */\n    private static Pattern getPattern() {\n        String pattern;\n        final ArrayList<String> operators = new ArrayList<String>(OPERATORS.keySet());\n        Collections.sort(operators, Collections.<String>reverseOrder());\n        StringBuilder sb = new StringBuilder(\"((\");\n        for (String op : operators) {\n            sb.append(Pattern.quote(op)).append(\"|\");\n        }\n        sb.deleteCharAt(sb.length() - 1);\n        sb.append(\")(?=[ _])|(?<![\\\\$:])(\").append(StringUtils.join(PREDEFINED_VARS.keySet(), \"|\")).append(\"))\");\n        pattern = sb.toString();\n        return Pattern.compile(pattern);\n    }\n\n    public Transformation getParent() {\n        return parent;\n    }\n\n    public T setParent(Transformation parent) {\n        this.parent = parent;\n        return (T) this;\n    }\n\n    public String serialize() {\n        return normalize(StringUtils.join(expressions, \"_\"));\n    }\n\n    @Override\n    public String toString() {\n        return serialize();\n    }\n\n    @SuppressWarnings(\"MethodDoesntCallSuperMethod\")\n    @Override\n    public T clone() {\n        T newCondition = newInstance();\n        newCondition.expressions.addAll(expressions);\n        newCondition.parent = parent;\n        return newCondition;\n    }\n\n    public T multiple(Object value) {\n        expressions.add(\"mul\");\n        expressions.add(value.toString());\n        return (T) this;\n    }\n\n    abstract protected T newInstance();\n\n    public T gt(Object value) {\n        return (T) this.gt().value(value);\n    }\n\n    public T gt() {\n        expressions.add(\"gt\");\n        return (T) this;\n    }\n\n    public T and(Object value) {\n        return (T) and().value(value);\n    }\n\n    public T and() {\n        expressions.add(\"and\");\n        return (T) this;\n    }\n\n    public T or(Object value) {\n        return (T) or().value(value);\n    }\n\n    public T or() {\n        expressions.add(\"or\");\n        return (T) this;\n    }\n\n    public T eq(Object value) {\n        return (T) eq().value(value);\n    }\n\n    public T eq() {\n        expressions.add(\"eq\");\n        return (T) this;\n    }\n\n    public T ne(Object value) {\n        return (T) ne().value(value);\n    }\n\n    public T ne() {\n        expressions.add(\"ne\");\n        return (T) this;\n    }\n\n    public T lt(Object value) {\n        return (T) lt().value(value);\n    }\n\n    public T lt() {\n        expressions.add(\"lt\");\n        return (T) this;\n    }\n\n    public T lte(Object value) {\n        return (T) lte().value(value);\n    }\n\n    public T lte() {\n        expressions.add(\"lte\");\n        return (T) this;\n    }\n\n    public T gte(Object value) {\n        return (T) gte().value(value);\n    }\n\n    public T gte() {\n        expressions.add(\"gte\");\n        return (T) this;\n    }\n\n    public T div(Object value) {\n        return (T) div().value(value);\n    }\n\n    public T div() {\n        expressions.add(\"div\");\n        return (T) this;\n    }\n\n    public T add(Object value) {\n        return (T) add().value(value);\n    }\n\n    public T add() {\n        expressions.add(\"add\");\n        return (T) this;\n    }\n\n    public T sub(Object value) {\n        return (T) sub().value(value);\n    }\n\n    public T sub() {\n        expressions.add(\"sub\");\n        return (T) this;\n    }\n\n    /**\n     * Utility shortcut method which invokes on this Expression instance {@link #pow()} method, takes its result and\n     * invokes {@link #value(Object)} method on it. Effectively, invocation of this shortcut results in\n     * \"to the power of value\" sub-expression added to the end of current expression instance.\n     *\n     * @param value argument for {@link #value(Object)} call\n     * @return result of {@link #value(Object)} call\n     */\n    public T pow(Object value) {\n        return (T) pow().value(value);\n    }\n\n    /**\n     * Adds \"to the power of\" sub-expression to the end of the list of already present sub-expressions in this\n     * expression instance.\n     *\n     * @return this expression instance\n     */\n    public T pow() {\n        expressions.add(\"pow\");\n        return (T) this;\n    }\n\n    public T value(Object value) {\n        expressions.add(String.valueOf(value));\n        return (T) this;\n    }\n}\n"
  },
  {
    "path": "cloudinary-core/src/main/java/com/cloudinary/transformation/Condition.java",
    "content": "package com.cloudinary.transformation;\n\nimport com.cloudinary.Transformation;\n\n/**\n * Represents a condition for {@link Transformation#ifCondition()}\n */\npublic class Condition extends BaseExpression<Condition> {\n\n    public Condition() {\n        super();\n\n    }\n\n    /**\n     * Create a Condition Object. The conditionStr string will be translated to a serialized condition.\n     * <br>\n     * For example, <code>new Condition(\"fc &gt; 3\")</code>\n     * @param conditionStr condition in string format\n     */\n    public Condition(String conditionStr) {\n        this();\n        if (conditionStr != null) {\n            expressions.add(normalize(conditionStr));\n        }\n    }\n\n    @Override\n    protected Condition newInstance() {\n        return new Condition();\n    }\n\n    protected Condition predicate(String name, String operator, Object value) {\n        if (OPERATORS.containsKey(operator)) {\n            operator = (String) OPERATORS.get(operator);\n        }\n        expressions.add(String.format(\"%s_%s_%s\", name, operator, value));\n        return this;\n    }\n\n    /**\n     * Terminates the definition of the condition and continue with Transformation definition.\n     * @return the Transformation object this Condition is attached to.\n     */\n    public Transformation then() {\n        getParent().ifCondition(serialize());\n        return getParent();\n    }\n\n    public Condition width(String operator, Object value) {\n        return predicate(\"w\", operator, value);\n    }\n\n    public Condition height(String operator, Object value) {\n        return predicate(\"h\", operator, value);\n    }\n\n    public Condition aspectRatio(String operator, Object value) {\n        return predicate(\"ar\", operator, value);\n    }\n    public Condition duration(String operator, Object value) {\n        return predicate(\"du\", operator, value);\n    }\n    public Condition initialDuration(String operator, Object value) {\n        return predicate(\"idu\", operator, value);\n    }\n\n    public Condition faceCount(String operator, Object value) {\n        return predicate(\"fc\", operator, value);\n    }\n\n    public Condition pageCount(String operator, Object value) {\n        return predicate(\"pc\", operator, value);\n    }\n\n}\n"
  },
  {
    "path": "cloudinary-core/src/main/java/com/cloudinary/transformation/Expression.java",
    "content": "package com.cloudinary.transformation;\n\n/**\n * Represents a transformation parameter expression.\n */\npublic class Expression extends BaseExpression<Expression> {\n\n    private boolean predefined = false;\n\n    public Expression(){\n        super();\n    }\n\n    public Expression(String name){\n        super();\n        expressions.add(name);\n    }\n\n    public static Expression variable(String name, Object value){\n        Expression var = new Expression(name);\n        var.expressions.add(value.toString());\n        return var;\n    }\n\n    public static Expression faceCount() {\n        return new Expression(\"fc\");\n    }\n\n    @Override\n    protected Expression newInstance() {\n        return new Expression();\n    }\n    /*\n    * @returns a new expression with the predefined variable \"width\"\n     */\n    public static Expression width() {\n        return new Expression(\"width\");\n    }\n    /*\n    * @returns a new expression with the predefined variable \"height\"\n     */\n    public static Expression height() {\n        return new Expression(\"height\");\n    }\n    /*\n    * @returns a new expression with the predefined variable \"initialWidth\"\n     */\n    public static Expression initialWidth() {\n        return new Expression(\"initialWidth\");\n    }\n    /*\n    * @returns a new expression with the predefined variable \"initialHeight\"\n     */\n    public static Expression initialHeight() {\n        return new Expression(\"initialHeight\");\n    }\n    /*\n    * @returns a new expression with the predefined variable \"aspectRatio\"\n     */\n    public static Expression aspectRatio() {\n        return new Expression(\"aspectRatio\");\n    }\n    /*\n    * @returns a new expression with the predefined variable \"initialAspectRatio\"\n     */\n    public static Expression initialAspectRatio() {\n        return new Expression(\"initialAspectRatio\");\n    }\n    /*\n    * @returns a new expression with the predefined variable \"pageCount\"\n     */\n    public static Expression pageCount() {\n        return new Expression(\"pageCount\");\n    }\n    /*\n    * @returns a new expression with the predefined variable \"currentPage\"\n     */\n    public static Expression currentPage() {\n        return new Expression(\"currentPage\");\n    }\n    /*\n    * @returns a new expression with the predefined variable \"tags\"\n     */\n    public static Expression tags() {\n        return new Expression(\"tags\");\n    }\n    /*\n    * @returns a new expression with the predefined variable \"pageX\"\n     */\n    public static Expression pageX() {\n        return new Expression(\"pageX\");\n    }\n    /*\n    * @returns a new expression with the predefined variable \"pageY\"\n     */\n    public static Expression pageY() {\n        return new Expression(\"pageY\");\n    }\n}\n"
  },
  {
    "path": "cloudinary-core/src/main/java/com/cloudinary/transformation/FetchLayer.java",
    "content": "package com.cloudinary.transformation;\n\nimport com.cloudinary.utils.Base64Coder;\n\npublic class FetchLayer extends AbstractLayer<FetchLayer> {\n\n    public FetchLayer() {\n        this.type = \"fetch\";\n    }\n\n    public FetchLayer url(String remoteUrl) {\n        this.publicId = Base64Coder.encodeURLSafeString(remoteUrl);;\n        return this;\n    }\n\n    @Override\n    public FetchLayer type(String type) {\n        throw new UnsupportedOperationException(\"Cannot modify type for fetch layers\");\n    }\n\n    @Override\n    FetchLayer getThis() {\n        return this;\n    }\n}\n"
  },
  {
    "path": "cloudinary-core/src/main/java/com/cloudinary/transformation/Layer.java",
    "content": "package com.cloudinary.transformation;\n\npublic class Layer extends AbstractLayer<Layer> {\n    @Override\n    Layer getThis() {\n        return this;\n    }\n}"
  },
  {
    "path": "cloudinary-core/src/main/java/com/cloudinary/transformation/SubtitlesLayer.java",
    "content": "package com.cloudinary.transformation;\n\npublic class SubtitlesLayer extends TextLayer {\n    public SubtitlesLayer() {\n        this.resourceType = \"subtitles\";\n    }\n}\n"
  },
  {
    "path": "cloudinary-core/src/main/java/com/cloudinary/transformation/TextLayer.java",
    "content": "package com.cloudinary.transformation;\n\nimport java.util.ArrayList;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\nimport com.cloudinary.SmartUrlEncoder;\nimport com.cloudinary.utils.StringUtils;\n\npublic class TextLayer extends AbstractLayer<TextLayer> {\n    protected String resourceType = \"text\";\n    protected String fontFamily = null;\n    protected Integer fontSize = null;\n    protected String fontWeight = null;\n    protected String fontStyle = null;\n    protected String fontAntialiasing = null;\n    protected String fontHinting=null;\n    protected String textDecoration = null;\n    protected String textAlign = null;\n    protected String stroke = null;\n    protected String letterSpacing = null;\n    protected Integer lineSpacing = null;\n    protected String text = null;\n    protected Object textStyle = null;\n\n    @Override\n    TextLayer getThis() {\n        return this;\n    }\n\n    public TextLayer resourceType(String resourceType) {\n        throw new UnsupportedOperationException(\"Cannot modify resourceType for text layers\");\n    }\n\n    public TextLayer type(String type) {\n        throw new UnsupportedOperationException(\"Cannot modify type for text layers\");\n    }\n\n    public TextLayer format(String format) {\n        throw new UnsupportedOperationException(\"Cannot modify format for text layers\");\n    }\n\n    public TextLayer fontFamily(String fontFamily) {\n        this.fontFamily = fontFamily;\n        return getThis();\n    }\n\n    public TextLayer fontAntialiasing(String fontAntialiasing) {\n        this.fontAntialiasing = fontAntialiasing;\n        return getThis();\n    }\n\n    public TextLayer fontHinting(String fontHinting) {\n        this.fontHinting = fontHinting;\n        return getThis();\n    }\n\n\n    public TextLayer fontSize(int fontSize) {\n        this.fontSize = fontSize;\n        return getThis();\n    }\n\n    public TextLayer fontWeight(String fontWeight) {\n        this.fontWeight = fontWeight;\n        return getThis();\n    }\n\n    public TextLayer fontStyle(String fontStyle) {\n        this.fontStyle = fontStyle;\n        return getThis();\n    }\n\n    public TextLayer textDecoration(String textDecoration) {\n        this.textDecoration = textDecoration;\n        return getThis();\n    }\n\n    public TextLayer textAlign(String textAlign) {\n        this.textAlign = textAlign;\n        return getThis();\n    }\n\n    public TextLayer stroke(String stroke) {\n        this.stroke = stroke;\n        return getThis();\n    }\n\n    public TextLayer letterSpacing(String letterSpacing) {\n        this.letterSpacing = letterSpacing;\n        return getThis();\n    }\n\n    public TextLayer letterSpacing(int letterSpacing) {\n        this.letterSpacing = String.valueOf(letterSpacing);\n        return getThis();\n    }\n\n    public TextLayer lineSpacing(Integer lineSpacing) {\n        this.lineSpacing = lineSpacing;\n        return getThis();\n    }\n\n    public TextLayer text(String text) {\n        String part;\n        StringBuffer result = new StringBuffer();\n        // Don't encode interpolation expressions e.g. $(variable)\n        Matcher m = Pattern.compile(\"\\\\$\\\\([a-zA-Z]\\\\w+\\\\)\").matcher(text);\n        int start = 0;\n        while (m.find()) {\n            part = text.substring(start, m.start());\n            part = SmartUrlEncoder.encode(part);\n            result.append(part); // append encoded pre-match\n            result.append(m.group()); // append match\n            start = m.end();\n        }\n        result.append(SmartUrlEncoder.encode(text.substring(start)));\n        this.text = result.toString().replace(\"%2C\", \"%252C\").replace(\"/\", \"%252F\");\n        return getThis();\n    }\n\n    /**\n     * Sets a text style identifier,\n     * Note: If this is used, all other style attributes are ignored in favor of this identifier\n     * @param textStyleIdentifier A variable string or an explicit style (e.g. \"Arial_17_bold_antialias_best\")\n     * @return Itself for chaining\n     */\n    public TextLayer textStyle(String textStyleIdentifier) {\n        this.textStyle = textStyleIdentifier;\n        return getThis();\n    }\n\n    /**\n     * Sets a text style identifier using an expression.\n     * Note: If this is used, all other style attributes are ignored in favor of this identifier\n     * @param textStyleIdentifier An expression instance referencing the style.\n     * @return Itself for chaining\n     */\n    public TextLayer textStyle(Expression textStyleIdentifier) {\n        this.textStyle = textStyleIdentifier;\n        return getThis();\n    }\n\n    @Override\n    public String toString() {\n        if (this.publicId == null && this.text == null) {\n            throw new IllegalArgumentException(\"Must supply either text or public_id.\");\n        }\n\n        ArrayList<String> components = new ArrayList<String>();\n        components.add(this.resourceType);\n\n        String styleIdentifier = textStyleIdentifier();\n        if (styleIdentifier != null) {\n            components.add(styleIdentifier);\n        }\n\n        if (this.publicId != null) {\n            components.add(this.formattedPublicId());\n        }\n\n        if (this.text != null) {\n            components.add(this.text);\n        }\n\n        return StringUtils.join(components, \":\");\n    }\n\n    protected String textStyleIdentifier() {\n        // Note: if a text-style argument is provided as a whole, it overrides everything else, no mix and match.\n        if (StringUtils.isNotBlank(this.textStyle)) {\n            return textStyle.toString();\n        }\n\n        ArrayList<String> components = new ArrayList<String>();\n\n        if (StringUtils.isNotBlank(this.fontWeight) && !this.fontWeight.equals(\"normal\"))\n            components.add(this.fontWeight);\n        if (StringUtils.isNotBlank(this.fontStyle) && !this.fontStyle.equals(\"normal\"))\n            components.add(this.fontStyle);\n        if (StringUtils.isNotBlank(this.fontAntialiasing))\n            components.add(\"antialias_\"+this.fontAntialiasing);\n        if (StringUtils.isNotBlank(this.fontHinting))\n            components.add(\"hinting_\"+this.fontHinting);\n        if (StringUtils.isNotBlank(this.textDecoration) && !this.textDecoration.equals(\"none\"))\n            components.add(this.textDecoration);\n        if (StringUtils.isNotBlank(this.textAlign))\n            components.add(this.textAlign);\n        if (StringUtils.isNotBlank(this.stroke) && !this.stroke.equals(\"none\"))\n            components.add(this.stroke);\n        if (StringUtils.isNotBlank(this.letterSpacing))\n            components.add(\"letter_spacing_\" + this.letterSpacing);\n        if (this.lineSpacing != null)\n            components.add(\"line_spacing_\" + this.lineSpacing.toString());\n\n        if (this.fontFamily == null && this.fontSize == null && components.isEmpty()) {\n            return null;\n        }\n\n        if (this.fontFamily == null) {\n            throw new IllegalArgumentException(\"Must supply fontFamily.\");\n        }\n\n        if (this.fontSize == null) {\n            throw new IllegalArgumentException(\"Must supply fontSize.\");\n        }\n\n        components.add(0, Integer.toString(this.fontSize));\n        components.add(0, this.fontFamily);\n\n        return StringUtils.join(components, \"_\");\n    }\n}\n"
  },
  {
    "path": "cloudinary-core/src/main/java/com/cloudinary/utils/Analytics.java",
    "content": "package com.cloudinary.utils;\n\nimport com.cloudinary.Cloudinary;\n\nimport java.util.Arrays;\nimport java.util.List;\n\npublic class Analytics {\n    private String sdkTokenQueryKey = \"_a\"; //sdkTokenQueryKey\n    private String sdkQueryDelimiter = \"=\";\n    public String algoVersion = \"D\";\n    public String prodcut = \"A\";\n    public String SDKCode = \"\"; // Java = G, Android = F\n    public String SDKSemver = \"\"; // Calculate the SDK version .\n    public String techVersion = \"\"; // Calculate the Java version.\n    public String osType;\n    public String osVersion;\n\n    public String featureFlag = \"0\";\n\n    public Analytics() {\n        this(\"G\", Cloudinary.VERSION,System.getProperty(\"java.version\"), \"Z\", \"0.0\", \"0\");\n    }\n    public Analytics(String sdkCode, String sdkVersion, String techVersion, String osType, String osVersion, String featureFlag) {\n        this.SDKCode = sdkCode;\n        this.SDKSemver = sdkVersion;\n        this.techVersion = techVersion;\n        this.osType = osType;\n        this.osVersion = osVersion;\n        this.featureFlag = featureFlag;\n    }\n\n    public Analytics setSDKCode(String SDKCode) {\n        this.SDKCode = SDKCode;\n        return this;\n    }\n\n    public Analytics setSDKSemver(String SDKSemver) {\n        this.SDKSemver = SDKSemver;\n        return this;\n    }\n\n    public Analytics setTechVersion(String techVersion) {\n        this.techVersion = techVersion;\n        return this;\n    }\n\n    public Analytics setFeatureFlag(String flag) {\n        this.featureFlag = flag;\n        return this;\n    }\n\n    /**\n     * Function turn analytics variables into viable query parameter.\n     * @return query param with analytics values.\n     */\n    public String toQueryParam() {\n        try {\n            return sdkTokenQueryKey + sdkQueryDelimiter + getAlgorithmVersion() + prodcut + getSDKType() + getSDKVersion() + getTechVersion() + getOsType() + getOsVersion() + getSDKFeatureFlag();\n        } catch (Exception e) {\n            return sdkTokenQueryKey + sdkQueryDelimiter + \"E\";\n        }\n    }\n\n    private String getTechVersion() throws Exception {\n        String[] techVersionString = techVersion.split(\"_\");\n        String[] versions = techVersionString[0].split(\"\\\\.\");\n        return versionArrayToString(versions);\n    }\n\n    private String versionArrayToString(String[] versions) throws Exception {\n        if (versions.length > 2) {\n            versions = Arrays.copyOf(versions, versions.length - 1);\n        }\n        return getPaddedString(StringUtils.join(versions, \".\"));\n    }\n\n    private String versionArrayToOsString(String[] versions) throws  Exception {\n        if (versions.length > 2) {\n            versions = Arrays.copyOf(versions, versions.length - 1);\n        }\n        return getOsVersionString(StringUtils.join(versions, \".\"));\n    }\n\n    private String getOsType() {\n        return (osType != null) ? osType : \"Z\"; //System.getProperty(\"os.name\");\n    }\n\n    private String getOsVersion() throws Exception {\n        return (osVersion != null) ? versionArrayToOsString(osVersion.split(\"\\\\.\")) : versionArrayToString(System.getProperty(\"os.version\").split(\"\\\\.\"));\n    }\n\n    private String getSDKType() {\n        return SDKCode;\n    }\n\n    private String getAlgorithmVersion() {\n        return algoVersion;\n    }\n\n    private String getSDKFeatureFlag() {\n        return featureFlag;\n    }\n\n    private String getSDKVersion() throws Exception {\n        return getPaddedString(SDKSemver);\n    }\n\n    private String getOsVersionString(String string) throws Exception {\n        String[] parts = string.split(\"\\\\.\");\n        String result = \"\";\n        for(int i = 0 ; i < parts.length ; i++) {\n            int num = Integer.parseInt(parts[i]);\n            String binaryString = Integer.toBinaryString(num);\n            binaryString = StringUtils.padStart(binaryString, 6, '0');\n            result = result + Base64Map.values.get(binaryString);\n        }\n        return result;\n    }\n\n    private String getPaddedString(String string) throws Exception {\n        String paddedReversedSemver = \"\";\n        int parts = string.split(\"\\\\.\").length;\n        int paddedStringLength = parts * 6;\n        try {\n            paddedReversedSemver = reverseVersion(string);\n        } catch (Exception e) {\n            throw new Exception(\"Error\");\n        }\n        int num = Integer.parseInt(StringUtils.join(paddedReversedSemver.split(\"\\\\.\"),\"\"));\n\n        String paddedBinary = StringUtils.padStart(Integer.toBinaryString(num), paddedStringLength, '0');\n\n        if (paddedBinary.length() % 6 != 0) {\n            throw new Exception(\"Error\");\n        }\n\n        String result = \"\";\n        List<String> resultList = StringUtils.getAllSubStringWithSize(paddedBinary,6);\n        int i = 0;\n        while (i < resultList.size()) {\n            result = result + Base64Map.values.get(resultList.get(i));\n            i++;\n        }\n        return result;\n    }\n\n    private String reverseVersion(String SDKSemver) throws Exception {\n        if (SDKSemver.split(\"\\\\.\").length < 2) {\n            throw new Exception(\"invalid semVer, must have at least two segments\");\n        }\n        String[] versionArray = SDKSemver.split(\"\\\\.\");\n        for (int i = 0 ; i < versionArray.length; i ++) {\n            versionArray[i] = StringUtils.padStart(versionArray[i], 2, '0');\n        }\n        return StringUtils.join(StringUtils.reverseStringArray(versionArray), \".\");\n    }\n}\n"
  },
  {
    "path": "cloudinary-core/src/main/java/com/cloudinary/utils/Base64Coder.java",
    "content": "//Copyright 2003-2010 Christian d'Heureuse, Inventec Informatik AG, Zurich, Switzerland\n//www.source-code.biz, www.inventec.ch/chdh\n//\n//This module is multi-licensed and may be used under the terms\n//of any of the following licenses:\n//\n//EPL, Eclipse Public License, V1.0 or later, http://www.eclipse.org/legal\n//LGPL, GNU Lesser General Public License, V2.1 or later, http://www.gnu.org/licenses/lgpl.html\n//GPL, GNU General Public License, V2 or later, http://www.gnu.org/licenses/gpl.html\n//AGPL, GNU Affero General Public License V3 or later, http://www.gnu.org/licenses/agpl.html\n//AL, Apache License, V2.0 or later, http://www.apache.org/licenses\n//BSD, BSD License, http://www.opensource.org/licenses/bsd-license.php\n//MIT, MIT License, http://www.opensource.org/licenses/MIT\n//\n//Please contact the author if you need another license.\n//This module is provided \"as is\", without warranties of any kind.\npackage com.cloudinary.utils;\n\n/**\n * A Base64 encoder/decoder.\n * <p>\n * <p>\n * This class is used to encode and decode data in Base64 format as described in\n * RFC 1521.\n *\n * @author Christian d'Heureuse, Inventec Informatik AG, Zurich, Switzerland,\n *         www.source-code.biz\n */\npublic class Base64Coder {\n\n    // The line separator string of the operating system.\n    private static final String systemLineSeparator = System\n            .getProperty(\"line.separator\");\n\n    // Mapping table from 6-bit nibbles to Base64 characters.\n    private static final char[] map1 = new char[64];\n\n    static {\n        int i = 0;\n        for (char c = 'A'; c <= 'Z'; c++)\n            map1[i++] = c;\n        for (char c = 'a'; c <= 'z'; c++)\n            map1[i++] = c;\n        for (char c = '0'; c <= '9'; c++)\n            map1[i++] = c;\n        map1[i++] = '+';\n        map1[i++] = '/';\n    }\n\n    // Mapping table from Base64 characters to 6-bit nibbles.\n    private static final byte[] map2 = new byte[128];\n\n    static {\n        for (int i = 0; i < map2.length; i++)\n            map2[i] = -1;\n        for (int i = 0; i < 64; i++)\n            map2[map1[i]] = (byte) i;\n    }\n\n    /**\n     * Encodes a string into Base64 format. No blanks or line breaks are\n     * inserted.\n     *\n     * @param s A String to be encoded.\n     * @return A String containing the Base64 encoded data.\n     */\n    public static String encodeString(String s) {\n        return new String(encode(s.getBytes()));\n    }\n\n    /**\n     * Encodes a byte array into Base 64 format and breaks the output into lines\n     * of 76 characters. This method is compatible with\n     * {@code sun.misc.BASE64Encoder.encodeBuffer(byte[])}.\n     *\n     * @param in An array containing the data bytes to be encoded.\n     * @return A String containing the Base64 encoded data, broken into lines.\n     */\n    public static String encodeLines(byte[] in) {\n        return encodeLines(in, 0, in.length, 76, systemLineSeparator);\n    }\n\n    /**\n     * Encodes a byte array into Base 64 format and breaks the output into\n     * lines.\n     *\n     * @param in            An array containing the data bytes to be encoded.\n     * @param iOff          Offset of the first byte in {@code in} to be processed.\n     * @param iLen          Number of bytes to be processed in {@code in}, starting\n     *                      at {@code iOff}.\n     * @param lineLen       Line length for the output data. Should be a multiple of 4.\n     * @param lineSeparator The line separator to be used to separate the output lines.\n     * @return A String containing the Base64 encoded data, broken into lines.\n     */\n    public static String encodeLines(\n            byte[] in, int iOff, int iLen,\n            int lineLen, String lineSeparator) {\n        int blockLen = (lineLen * 3) / 4;\n        if (blockLen <= 0)\n            throw new IllegalArgumentException();\n        int lines = (iLen + blockLen - 1) / blockLen;\n        int bufLen = ((iLen + 2) / 3) * 4 + lines * lineSeparator.length();\n        StringBuilder buf = new StringBuilder(bufLen);\n        int ip = 0;\n        while (ip < iLen) {\n            int l = Math.min(iLen - ip, blockLen);\n            buf.append(encode(in, iOff + ip, l));\n            buf.append(lineSeparator);\n            ip += l;\n        }\n        return buf.toString();\n    }\n\n    /**\n     * Encodes a byte array into Base64 format. No blanks or line breaks are\n     * inserted in the output.\n     *\n     * @param in An array containing the data bytes to be encoded.\n     * @return A character array containing the Base64 encoded data.\n     */\n    public static char[] encode(byte[] in) {\n        return encode(in, 0, in.length);\n    }\n\n    /**\n     * Encodes a byte array into Base64 format. No blanks or line breaks are\n     * inserted in the output.\n     *\n     * @param in   An array containing the data bytes to be encoded.\n     * @param iLen Number of bytes to process in {@code in}.\n     * @return A character array containing the Base64 encoded data.\n     */\n    public static char[] encode(byte[] in, int iLen) {\n        return encode(in, 0, iLen);\n    }\n\n    /**\n     * Encodes a byte array into Base64 format. No blanks or line breaks are\n     * inserted in the output.\n     *\n     * @param in   An array containing the data bytes to be encoded.\n     * @param iOff Offset of the first byte in {@code in} to be processed.\n     * @param iLen Number of bytes to process in {@code in}, starting at\n     *             {@code iOff}.\n     * @return A character array containing the Base64 encoded data.\n     */\n    public static char[] encode(byte[] in, int iOff, int iLen) {\n        int oDataLen = (iLen * 4 + 2) / 3; // output length without padding\n        int oLen = ((iLen + 2) / 3) * 4; // output length including padding\n        char[] out = new char[oLen];\n        int ip = iOff;\n        int iEnd = iOff + iLen;\n        int op = 0;\n        while (ip < iEnd) {\n            int i0 = in[ip++] & 0xff;\n            int i1 = ip < iEnd ? in[ip++] & 0xff : 0;\n            int i2 = ip < iEnd ? in[ip++] & 0xff : 0;\n            int o0 = i0 >>> 2;\n            int o1 = ((i0 & 3) << 4) | (i1 >>> 4);\n            int o2 = ((i1 & 0xf) << 2) | (i2 >>> 6);\n            int o3 = i2 & 0x3F;\n            out[op++] = map1[o0];\n            out[op++] = map1[o1];\n            out[op] = op < oDataLen ? map1[o2] : '=';\n            op++;\n            out[op] = op < oDataLen ? map1[o3] : '=';\n            op++;\n        }\n        return out;\n    }\n\n    /**\n     * Decodes a string from Base64 format. No blanks or line breaks are allowed\n     * within the Base64 encoded input data.\n     *\n     * @param s A Base64 String to be decoded.\n     * @return A String containing the decoded data.\n     * @throws IllegalArgumentException If the input is not valid Base64 encoded data.\n     */\n    public static String decodeString(String s) {\n        return new String(decode(s));\n    }\n\n    /**\n     * Decodes a byte array from Base64 format and ignores line separators, tabs\n     * and blanks. CR, LF, Tab and Space characters are ignored in the input\n     * data. This method is compatible with\n     * {@code sun.misc.BASE64Decoder.decodeBuffer(String)}.\n     *\n     * @param s A Base64 String to be decoded.\n     * @return An array containing the decoded data bytes.\n     * @throws IllegalArgumentException If the input is not valid Base64 encoded data.\n     */\n    public static byte[] decodeLines(String s) {\n        char[] buf = new char[s.length()];\n        int p = 0;\n        for (int ip = 0; ip < s.length(); ip++) {\n            char c = s.charAt(ip);\n            if (c != ' ' && c != '\\r' && c != '\\n' && c != '\\t')\n                buf[p++] = c;\n        }\n        return decode(buf, 0, p);\n    }\n\n    /**\n     * Decodes a byte array from Base64 format. No blanks or line breaks are\n     * allowed within the Base64 encoded input data.\n     *\n     * @param s A Base64 String to be decoded.\n     * @return An array containing the decoded data bytes.\n     * @throws IllegalArgumentException If the input is not valid Base64 encoded data.\n     */\n    public static byte[] decode(String s) {\n        return decode(s.toCharArray());\n    }\n\n    /**\n     * Decodes a byte array from Base64 format. No blanks or line breaks are\n     * allowed within the Base64 encoded input data.\n     *\n     * @param in A character array containing the Base64 encoded data.\n     * @return An array containing the decoded data bytes.\n     * @throws IllegalArgumentException If the input is not valid Base64 encoded data.\n     */\n    public static byte[] decode(char[] in) {\n        return decode(in, 0, in.length);\n    }\n\n    /**\n     * Decodes a byte array from Base64 format. No blanks or line breaks are\n     * allowed within the Base64 encoded input data.\n     *\n     * @param in   A character array containing the Base64 encoded data.\n     * @param iOff Offset of the first character in {@code in} to be\n     *             processed.\n     * @param iLen Number of characters to process in {@code in}, starting\n     *             at {@code iOff}.\n     * @return An array containing the decoded data bytes.\n     * @throws IllegalArgumentException If the input is not valid Base64 encoded data.\n     */\n    public static byte[] decode(char[] in, int iOff, int iLen) {\n        if (iLen % 4 != 0)\n            throw new IllegalArgumentException(\n                    \"Length of Base64 encoded input string is not a multiple of 4.\");\n        while (iLen > 0 && in[iOff + iLen - 1] == '=')\n            iLen--;\n        int oLen = (iLen * 3) / 4;\n        byte[] out = new byte[oLen];\n        int ip = iOff;\n        int iEnd = iOff + iLen;\n        int op = 0;\n        while (ip < iEnd) {\n            int i0 = in[ip++];\n            int i1 = in[ip++];\n            int i2 = ip < iEnd ? in[ip++] : 'A';\n            int i3 = ip < iEnd ? in[ip++] : 'A';\n            if (i0 > 127 || i1 > 127 || i2 > 127 || i3 > 127)\n                throw new IllegalArgumentException(\n                        \"Illegal character in Base64 encoded data.\");\n            int b0 = map2[i0];\n            int b1 = map2[i1];\n            int b2 = map2[i2];\n            int b3 = map2[i3];\n            if (b0 < 0 || b1 < 0 || b2 < 0 || b3 < 0)\n                throw new IllegalArgumentException(\n                        \"Illegal character in Base64 encoded data.\");\n            int o0 = (b0 << 2) | (b1 >>> 4);\n            int o1 = ((b1 & 0xf) << 4) | (b2 >>> 2);\n            int o2 = ((b2 & 3) << 6) | b3;\n            out[op++] = (byte) o0;\n            if (op < oLen)\n                out[op++] = (byte) o1;\n            if (op < oLen)\n                out[op++] = (byte) o2;\n        }\n        return out;\n    }\n\n    // Dummy constructor.\n    private Base64Coder() {\n    }\n\n    public static String encodeURLSafeString(String s) {\n        return encodeURLSafeString(s.getBytes());\n    }\n\n    public static String encodeURLSafeString(byte[] digest) {\n        char[] encode = encode(digest);\n        for (int i = 0; i < encode.length; i++) {\n            if (encode[i] == '+') {\n                encode[i] = '-';\n            } else if (encode[i] == '/') {\n                encode[i] = '_';\n            }\n        }\n        return new String(encode);\n    }\n\n} // end class Base64Coder"
  },
  {
    "path": "cloudinary-core/src/main/java/com/cloudinary/utils/Base64Map.java",
    "content": "package com.cloudinary.utils;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\npublic final class Base64Map {\n    private Base64Map() {}\n\n    public static Map<String, String> values;\n\n    static {\n        values = new HashMap<>();\n        values.put(\"000000\", \"A\");\n        values.put(\"000001\", \"B\");\n        values.put(\"000010\", \"C\");\n        values.put(\"000011\", \"D\");\n        values.put(\"000100\", \"E\");\n        values.put(\"000101\", \"F\");\n        values.put(\"000110\", \"G\");\n        values.put(\"000111\", \"H\");\n        values.put(\"001000\", \"I\");\n        values.put(\"001001\", \"J\");\n        values.put(\"001010\", \"K\");\n        values.put(\"001011\", \"L\");\n        values.put(\"001100\", \"M\");\n        values.put(\"001101\", \"N\");\n        values.put(\"001110\", \"O\");\n        values.put(\"001111\", \"P\");\n        values.put(\"010000\", \"Q\");\n        values.put(\"010001\", \"R\");\n        values.put(\"010010\", \"S\");\n        values.put(\"010011\", \"T\");\n        values.put(\"010100\", \"U\");\n        values.put(\"010101\", \"V\");\n        values.put(\"010110\", \"W\");\n        values.put(\"010111\", \"X\");\n        values.put(\"011000\", \"Y\");\n        values.put(\"011001\", \"Z\");\n        values.put(\"011010\", \"a\");\n        values.put(\"011011\", \"b\");\n        values.put(\"011100\", \"c\");\n        values.put(\"011101\", \"d\");\n        values.put(\"011110\", \"e\");\n        values.put(\"011111\", \"f\");\n        values.put(\"100000\",\"g\");\n        values.put(\"100001\",\"h\");\n        values.put(\"100010\",\"i\");\n        values.put(\"100011\",\"j\");\n        values.put(\"100100\",\"k\");\n        values.put(\"100101\",\"l\");\n        values.put(\"100110\",\"m\");\n        values.put(\"100111\",\"n\");\n        values.put(\"101000\",\"o\");\n        values.put(\"101001\",\"p\");\n        values.put(\"101010\",\"q\");\n        values.put(\"101011\",\"r\");\n        values.put(\"101100\",\"s\");\n        values.put(\"101101\",\"t\");\n        values.put(\"101110\",\"u\");\n        values.put(\"101111\",\"v\");\n        values.put(\"110000\",\"w\");\n        values.put(\"110001\",\"x\");\n        values.put(\"110010\",\"y\");\n        values.put(\"110011\",\"z\");\n        values.put(\"110100\",\"0\");\n        values.put(\"110101\",\"1\");\n        values.put(\"110110\",\"2\");\n        values.put(\"110111\",\"3\");\n        values.put(\"111000\",\"4\");\n        values.put(\"111001\",\"5\");\n        values.put(\"111010\",\"6\");\n        values.put(\"111011\",\"7\");\n        values.put(\"111100\",\"8\");\n        values.put(\"111101\",\"9\");\n        values.put(\"111110\",\"+\");\n        values.put(\"111111\",\"/\");\n    }\n}\n"
  },
  {
    "path": "cloudinary-core/src/main/java/com/cloudinary/utils/HtmlEscape.java",
    "content": "package com.cloudinary.utils;\n\n\n/**\n * HtmlEscape in Java, which is compatible with utf-8\n *\n * @author Ulrich Jensen, http://www.htmlescape.net\n *         Feel free to get inspired, use or steal this code and use it in your\n *         own projects.\n *         License:\n *         You have the right to use this code in your own project or publish it\n *         on your own website.\n *         If you are going to use this code, please include the author lines.\n *         Use this code at your own risk. The author does not warrent or assume any\n *         legal liability or responsibility for the accuracy, completeness or usefullness of\n *         this program code.\n */\n\npublic final class HtmlEscape {\n    private HtmlEscape() {}\n\n    private static char[] hex = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};\n\n    /**\n     * Method for html escaping a String, for use in a textarea\n     *\n     * @param original The String to escape\n     * @return The escaped String\n     */\n    public static String escapeTextArea(String original) {\n        return escapeTags(escapeSpecial(original));\n    }\n\n    /**\n     * Normal escape function, for Html escaping Strings\n     *\n     * @param original The original String\n     * @return The escape String\n     */\n    public static String escape(String original) {\n        return escapeBr(escapeTags(escapeSpecial(original)));\n    }\n\n    public static String escapeTags(String original) {\n        if (original == null) return \"\";\n        StringBuffer out = new StringBuffer(\"\");\n        char[] chars = original.toCharArray();\n        for (int i = 0; i < chars.length; i++) {\n            boolean found = true;\n            switch (chars[i]) {\n                case 60:\n                    out.append(\"&lt;\");\n                    break; //<\n                case 62:\n                    out.append(\"&gt;\");\n                    break; //>\n                case 34:\n                    out.append(\"&quot;\");\n                    break; //\"\n                default:\n                    found = false;\n                    break;\n            }\n            if (!found) out.append(chars[i]);\n\n        }\n        return out.toString();\n\n    }\n\n    public static String escapeBr(String original) {\n        if (original == null) return \"\";\n        StringBuffer out = new StringBuffer(\"\");\n        char[] chars = original.toCharArray();\n        for (int i = 0; i < chars.length; i++) {\n            boolean found = true;\n            switch (chars[i]) {\n                case '\\n':\n                    out.append(\"<br/>\");\n                    break; //newline\n                case '\\r':\n                    break;\n                default:\n                    found = false;\n                    break;\n            }\n            if (!found) out.append(chars[i]);\n\n        }\n        return out.toString();\n    }\n\n    public static String escapeSpecial(String original) {\n        if (original == null) return \"\";\n        StringBuffer out = new StringBuffer(\"\");\n        char[] chars = original.toCharArray();\n        for (int i = 0; i < chars.length; i++) {\n            boolean found = true;\n            switch(chars[i]) {\n                // @formatter:off\n                case 38:out.append(\"&amp;\"); break; //&\n                case 198:out.append(\"&AElig;\"); break; //Æ\n                case 193:out.append(\"&Aacute;\"); break; //Á\n                case 194:out.append(\"&Acirc;\"); break; //Â\n                case 192:out.append(\"&Agrave;\"); break; //À\n                case 197:out.append(\"&Aring;\"); break; //Å\n                case 195:out.append(\"&Atilde;\"); break; //Ã\n                case 196:out.append(\"&Auml;\"); break; //Ä\n                case 199:out.append(\"&Ccedil;\"); break; //Ç\n                case 208:out.append(\"&ETH;\"); break; //Ð\n                case 201:out.append(\"&Eacute;\"); break; //É\n                case 202:out.append(\"&Ecirc;\"); break; //Ê\n                case 200:out.append(\"&Egrave;\"); break; //È\n                case 203:out.append(\"&Euml;\"); break; //Ë\n                case 205:out.append(\"&Iacute;\"); break; //Í\n                case 206:out.append(\"&Icirc;\"); break; //Î\n                case 204:out.append(\"&Igrave;\"); break; //Ì\n                case 207:out.append(\"&Iuml;\"); break; //Ï\n                case 209:out.append(\"&Ntilde;\"); break; //Ñ\n                case 211:out.append(\"&Oacute;\"); break; //Ó\n                case 212:out.append(\"&Ocirc;\"); break; //Ô\n                case 210:out.append(\"&Ograve;\"); break; //Ò\n                case 216:out.append(\"&Oslash;\"); break; //Ø\n                case 213:out.append(\"&Otilde;\"); break; //Õ\n                case 214:out.append(\"&Ouml;\"); break; //Ö\n                case 222:out.append(\"&THORN;\"); break; //Þ\n                case 218:out.append(\"&Uacute;\"); break; //Ú\n                case 219:out.append(\"&Ucirc;\"); break; //Û\n                case 217:out.append(\"&Ugrave;\"); break; //Ù\n                case 220:out.append(\"&Uuml;\"); break; //Ü\n                case 221:out.append(\"&Yacute;\"); break; //Ý\n                case 225:out.append(\"&aacute;\"); break; //á\n                case 226:out.append(\"&acirc;\"); break; //â\n                case 230:out.append(\"&aelig;\"); break; //æ\n                case 224:out.append(\"&agrave;\"); break; //à\n                case 229:out.append(\"&aring;\"); break; //å\n                case 227:out.append(\"&atilde;\"); break; //ã\n                case 228:out.append(\"&auml;\"); break; //ä\n                case 231:out.append(\"&ccedil;\"); break; //ç\n                case 233:out.append(\"&eacute;\"); break; //é\n                case 234:out.append(\"&ecirc;\"); break; //ê\n                case 232:out.append(\"&egrave;\"); break; //è\n                case 240:out.append(\"&eth;\"); break; //ð\n                case 235:out.append(\"&euml;\"); break; //ë\n                case 237:out.append(\"&iacute;\"); break; //í\n                case 238:out.append(\"&icirc;\"); break; //î\n                case 236:out.append(\"&igrave;\"); break; //ì\n                case 239:out.append(\"&iuml;\"); break; //ï\n                case 241:out.append(\"&ntilde;\"); break; //ñ\n                case 243:out.append(\"&oacute;\"); break; //ó\n                case 244:out.append(\"&ocirc;\"); break; //ô\n                case 242:out.append(\"&ograve;\"); break; //ò\n                case 248:out.append(\"&oslash;\"); break; //ø\n                case 245:out.append(\"&otilde;\"); break; //õ\n                case 246:out.append(\"&ouml;\"); break; //ö\n                case 223:out.append(\"&szlig;\"); break; //ß\n                case 254:out.append(\"&thorn;\"); break; //þ\n                case 250:out.append(\"&uacute;\"); break; //ú\n                case 251:out.append(\"&ucirc;\"); break; //û\n                case 249:out.append(\"&ugrave;\"); break; //ù\n                case 252:out.append(\"&uuml;\"); break; //ü\n                case 253:out.append(\"&yacute;\"); break; //ý\n                case 255:out.append(\"&yuml;\"); break; //ÿ\n                case 162:out.append(\"&cent;\"); break; //¢\n                // @formatter:on\n                default:\n                    found=false;\n                    break;\n            }\n            if (!found) {\n                if (chars[i] > 127) {\n                    char c = chars[i];\n                    int a4 = c % 16;\n                    c = (char) (c / 16);\n                    int a3 = c % 16;\n                    c = (char) (c / 16);\n                    int a2 = c % 16;\n                    c = (char) (c / 16);\n                    int a1 = c % 16;\n                    out.append(\"&#x\" + hex[a1] + hex[a2] + hex[a3] + hex[a4] + \";\");\n                } else {\n                    out.append(chars[i]);\n                }\n            }\n        }\n        return out.toString();\n    }\n\n} "
  },
  {
    "path": "cloudinary-core/src/main/java/com/cloudinary/utils/ObjectUtils.java",
    "content": "package com.cloudinary.utils;\n\nimport org.cloudinary.json.JSONArray;\nimport org.cloudinary.json.JSONException;\nimport org.cloudinary.json.JSONObject;\n\nimport java.io.*;\nimport java.text.DateFormat;\nimport java.text.ParseException;\nimport java.text.SimpleDateFormat;\nimport java.util.*;\n\n\npublic final class ObjectUtils {\n    private ObjectUtils() {}\n\n    /**\n     * Formats a Date as an ISO-8601 string representation.\n     * @param date Date to format\n     * @return The date formatted as ISO-8601 string\n     */\n    public static String toISO8601(Date date){\n        DateFormat dateFormat = getDateFormat();\n        return dateFormat.format(date);\n    }\n\n    public static Date fromISO8601(String date) throws ParseException {\n        DateFormat dateFormat = getDateFormat();\n        return (Date) dateFormat.parseObject(date);\n    }\n\n    private static DateFormat getDateFormat() {\n        DateFormat dateFormat = new SimpleDateFormat(\"yyyy-MM-dd'T'HH:mm:ssXXX\", Locale.US);\n        dateFormat.setTimeZone(TimeZone.getTimeZone(\"UTC\"));\n        return dateFormat;\n    }\n\n    public static String asString(Object value) {\n        if (value == null) {\n            return null;\n        } else {\n            return value.toString();\n        }\n    }\n\n    public static String asString(Object value, String defaultValue) {\n        if (value == null) {\n            return defaultValue;\n        } else {\n            return value.toString();\n        }\n    }\n\n    public static String serialize(Object object) throws IOException {\n        ByteArrayOutputStream baos = new ByteArrayOutputStream();\n        ObjectOutputStream objectOutputStream = new ObjectOutputStream(baos);\n        try {\n            objectOutputStream.writeObject(object);\n            return new String(Base64Coder.encode(baos.toByteArray()));\n        } finally {\n            objectOutputStream.close();\n        }\n    }\n\n    public static Object deserialize(String base64SerializedString) throws IOException, ClassNotFoundException {\n        byte[] buf = Base64Coder.decode(base64SerializedString);\n        return new ObjectInputStream(new ByteArrayInputStream(buf)).readObject();\n    }\n\n    @SuppressWarnings({\"rawtypes\", \"unchecked\"})\n    public static List asArray(Object value) {\n        if (value == null) {\n            return Collections.EMPTY_LIST;\n        } else if (value instanceof int[]) {\n            List array = new ArrayList();\n            for (int i : (int[]) value) {\n                array.add(new Integer(i));\n            }\n            return array;\n        } else if (value instanceof Object[]) {\n            return Arrays.asList((Object[]) value);\n        } else if (value instanceof List) {\n            return (List) value;\n        } else {\n            List array = new ArrayList();\n            array.add(value);\n            return array;\n        }\n    }\n\n    public static Boolean asBoolean(Object value, Boolean defaultValue) {\n        if (value == null) {\n            return defaultValue;\n        } else return asBoolean(value);\n    }\n\n    public static Boolean asBoolean(Object value) {\n        if (value instanceof Boolean) {\n            return (Boolean) value;\n        } else {\n            return \"true\".equals(value);\n        }\n    }\n\n    public static Float asFloat(Object value) {\n        if (value == null) {\n            return null;\n        } else if (value instanceof Float) {\n            return (Float) value;\n        } else {\n            return Float.parseFloat(value.toString());\n        }\n    }\n\n    @SuppressWarnings({\"rawtypes\", \"unchecked\"})\n    public static Map asMap(Object... values) {\n        if (values.length % 2 != 0)\n            throw new RuntimeException(\"Usage - (key, value, key, value, ...)\");\n        Map result = new HashMap(values.length / 2);\n        for (int i = 0; i < values.length; i += 2) {\n            result.put(values[i], values[i + 1]);\n        }\n        return result;\n    }\n\n    @SuppressWarnings(\"rawtypes\")\n    public static Map emptyMap() {\n        return Collections.EMPTY_MAP;\n    }\n\n    @SuppressWarnings({\"unchecked\", \"rawtypes\"})\n    public static String encodeMap(Object arg) {\n        if (arg != null && arg instanceof Map) {\n            Map<String, String> mapArg = (Map<String, String>) arg;\n            HashSet out = new HashSet();\n            for (Map.Entry<String, String> entry : mapArg.entrySet()) {\n                out.add(entry.getKey() + \"=\" + entry.getValue());\n            }\n            return StringUtils.join(out.toArray(), \"|\");\n        } else if (arg == null) {\n            return null;\n        } else {\n            return arg.toString();\n        }\n    }\n\n    public static Map<String, ? extends Object> only(Map<String, ? extends Object> hash, String... keys) {\n        Map<String, Object> result = new HashMap<String, Object>();\n        for (String key : keys) {\n            if (hash.containsKey(key)) {\n                result.put(key, hash.get(key));\n            }\n        }\n        return result;\n    }\n\n    @SuppressWarnings(\"rawtypes\")\n    public static Map<String, Object> toMap(JSONObject object) throws JSONException {\n        @SuppressWarnings(\"unchecked\")\n        Map<String, Object> map = new HashMap();\n        Iterator keys = object.keys();\n        while (keys.hasNext()) {\n            String key = (String) keys.next();\n            map.put(key, fromJson(object.get(key)));\n        }\n        return map;\n    }\n\n    public static JSONObject toJSON(Map<String, ? extends Object> map) throws JSONException {\n        JSONObject json = new JSONObject();\n        for (Map.Entry<String, ? extends Object> entry : map.entrySet()) {\n            String field = entry.getKey();\n            Object value = entry.getValue();\n            json.put(field, value);\n        }\n        return json;\n    }\n\n    private static Object fromJson(Object json) throws JSONException {\n        if (json == JSONObject.NULL) {\n            return null;\n        } else if (json instanceof JSONObject) {\n            return toMap((JSONObject) json);\n        } else if (json instanceof JSONArray) {\n            return toList((JSONArray) json);\n        } else {\n            return json;\n        }\n    }\n\n    @SuppressWarnings({\"rawtypes\", \"unchecked\"})\n    public static List toList(JSONArray array) throws JSONException {\n        List list = new ArrayList();\n        for (int i = 0; i < array.length(); i++) {\n            list.add(fromJson(array.get(i)));\n        }\n        return list;\n    }\n\n    public static Integer asInteger(Object value, Integer defaultValue) {\n        if (value == null) {\n            return defaultValue;\n        } else if (value instanceof Integer) {\n            return (Integer) value;\n        } else {\n            return Integer.parseInt(value.toString());\n        }\n    }\n\n    public static Long asLong(Object value, Long defaultValue) {\n        if (value == null) {\n            return defaultValue;\n        } else if (value instanceof Long) {\n            return (Long) value;\n        } else {\n            return Long.parseLong(value.toString());\n        }\n    }\n\n    public static String toUsageApiDateFormat(Date date){\n        return new SimpleDateFormat(\"dd-MM-yyy\").format(date);\n    }\n\n    public static String toISO8601DateOnly(Date date) {\n        return new SimpleDateFormat(\"yyyy-MM-dd\").format(date);\n    }\n\n    public static Date fromISO8601DateOnly(String string) throws ParseException {\n        return new SimpleDateFormat(\"yyyy-MM-dd\").parse(string);\n    }\n}\n"
  },
  {
    "path": "cloudinary-core/src/main/java/com/cloudinary/utils/Rectangle.java",
    "content": "package com.cloudinary.utils;\n\nimport java.io.Serializable;\n\npublic class Rectangle implements Serializable{\n\n    public int height;\n    public int width;\n    public int y;\n    public int x;\n\n    public Rectangle(int x, int y, int width, int height) {\n        this.x = x;\n        this.y = y;\n        this.width = width;\n        this.height = height;\n    }\n\n}\n"
  },
  {
    "path": "cloudinary-core/src/main/java/com/cloudinary/utils/StringUtils.java",
    "content": "package com.cloudinary.utils;\n\nimport java.io.ByteArrayOutputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.nio.charset.Charset;\nimport java.util.*;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\npublic final class StringUtils {\n    private StringUtils() {}\n\n    public static final String EMPTY = \"\";\n\n    /**\n     * Join a list of Strings\n     *\n     * @param list      strings to join\n     * @param separator the separator to insert between the strings\n     * @return a string made of the strings in list separated by separator\n     */\n    public static String join(List<String> list, String separator) {\n        if (list == null) {\n            return null;\n        }\n\n        return join(list.toArray(), separator, 0, list.size());\n    }\n\n    /**\n     * Join a array of Strings\n     *\n     * @param array     strings to join\n     * @param separator the separator to insert between the strings\n     * @return a string made of the strings in array separated by separator\n     */\n    public static String join(Object[] array, String separator) {\n        if (array == null) {\n            return null;\n        }\n        return join(array, separator, 0, array.length);\n    }\n\n    /**\n     * Join a collection of Strings\n     *\n     * @param collection strings to join\n     * @param separator  the separator to insert between the strings\n     * @return a string made of the strings in collection separated by separator\n     */\n    public static String join(Collection<String> collection, String separator) {\n        if (collection == null) {\n            return null;\n        }\n        return join(collection.toArray(new String[collection.size()]), separator, 0, collection.size());\n    }\n\n    /**\n     * Join a array of Strings from startIndex to endIndex\n     *\n     * @param array      strings to join\n     * @param separator  the separator to insert between the strings\n     * @param startIndex the string to start from\n     * @param endIndex   the last string to join\n     * @return a string made of the strings in array separated by separator\n     */\n    public static String join(final Object[] array, String separator, final int startIndex, final int endIndex) {\n        if (array == null) {\n            return null;\n        }\n        if (separator == null) {\n            separator = EMPTY;\n        }\n\n        final int noOfItems = endIndex - startIndex;\n        if (noOfItems <= 0) {\n            return EMPTY;\n        }\n\n        final StringBuilder buf = new StringBuilder(noOfItems * 16);\n\n        for (int i = startIndex; i < endIndex; i++) {\n            if (i > startIndex) {\n                buf.append(separator);\n            }\n            if (array[i] != null) {\n                buf.append(array[i]);\n            }\n        }\n        return buf.toString();\n    }\n\n    final protected static char[] hexArray = \"0123456789abcdef\".toCharArray();\n\n    /**\n     * Convert an array of bytes to a string of hex values\n     *\n     * @param bytes bytes to convert\n     * @return a string of hex values.\n     */\n    public static String encodeHexString(byte[] bytes) {\n        char[] hexChars = new char[bytes.length * 2];\n        for (int j = 0; j < bytes.length; j++) {\n            int v = bytes[j] & 0xFF;\n            hexChars[j * 2] = hexArray[v >>> 4];\n            hexChars[j * 2 + 1] = hexArray[v & 0x0F];\n        }\n        return new String(hexChars);\n    }\n\n    /**\n     * Convert a string of hex values to an array of bytes\n     *\n     * @param s a string of two digit Hex numbers. The length of string to parse must be even.\n     * @return bytes representation of the string\n     */\n    public static byte[] hexStringToByteArray(String s) {\n        int len = s.length();\n        byte[] data = new byte[len / 2];\n\n        if (len % 2 != 0) {\n            throw new IllegalArgumentException(\"Length of string to parse must be even.\");\n        }\n\n        for (int i = 0; i < len; i += 2) {\n            data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit(s.charAt(i + 1), 16));\n        }\n\n        return data;\n    }\n\n    /**\n     * Method for html escaping a String\n     *\n     * @param input The String to escape\n     * @return The escaped String\n     * @see HtmlEscape#escapeTextArea(String)\n     */\n    public static String escapeHtml(String input) {\n        return HtmlEscape.escapeTextArea(input);\n    }\n\n    /**\n     * Verify that the input has non whitespace characters in it\n     *\n     * @param input a String-like object\n     * @return true if input has non whitespace characters in it\n     */\n    public static boolean isNotBlank(Object input) {\n        if (input == null) return false;\n        return !isBlank(input.toString());\n    }\n\n    /**\n     * Verify that the input has non whitespace characters in it\n     *\n     * @param input a String\n     * @return true if input has non whitespace characters in it\n     */\n    public static boolean isNotBlank(String input) {\n        return !isBlank(input);\n    }\n\n    /**\n     * Verify that the input has no characters\n     *\n     * @param input a string\n     * @return true if input is null or has no characters\n     */\n    public static boolean isEmpty(String input) {\n        return input == null || input.length() == 0;\n    }\n\n    /**\n     * Verify that the input is an empty string or contains only whitespace characters.<br>\n     * see {@link Character#isWhitespace(char)}\n     *\n     * @param input a string\n     * @return true if input is an empty string or contains only whitespace characters\n     */\n    public static boolean isBlank(String input) {\n        int strLen;\n        if (input == null || (strLen = input.length()) == 0) {\n            return true;\n        }\n        for (int i = 0; i < strLen; i++) {\n            if (!Character.isWhitespace(input.charAt(i))) {\n                return false;\n            }\n        }\n        return true;\n    }\n\n    /**\n     * Read the entire input stream in 1KB chunks\n     *\n     * @param in input stream to read from\n     * @return a String generated from the input stream\n     * @throws IOException thrown by the input stream\n     */\n    public static String read(InputStream in) throws IOException {\n        ByteArrayOutputStream baos = new ByteArrayOutputStream();\n        byte[] buffer = new byte[1024];\n        int length = 0;\n        while ((length = in.read(buffer)) != -1) {\n            baos.write(buffer, 0, length);\n        }\n        return new String(baos.toByteArray());\n    }\n\n    public static boolean isRemoteUrl(String file) {\n        return file.matches(\"ftp:.*|https?:.*|s3:.*|gs:.*|data:([\\\\w-]+/[\\\\w-]+(\\\\+[\\\\w-]+)?)?(;[\\\\w-]+=[\\\\w-]+)*;base64,([a-zA-Z0-9/+\\n=]+)\");\n    }\n\n    /**\n     * Replaces the unsafe characters in url with url-encoded values.\n     * This is based on {@link java.net.URLEncoder#encode(String, String)}\n     * @param url The url to encode\n     * @param unsafe Regex pattern of unsafe characters\n     * @param charset\n     * @return An encoded url string\n     */\n    public static String urlEncode(String url, Pattern unsafe, Charset charset) {\n        StringBuffer sb = new StringBuffer(url.length());\n        Matcher matcher = unsafe.matcher(url);\n        while (matcher.find()) {\n            String str = matcher.group(0);\n            byte[] bytes = str.getBytes(charset);\n            StringBuilder escaped = new StringBuilder(str.length() * 3);\n\n            for (byte aByte : bytes) {\n                escaped.append('%');\n                char ch = Character.forDigit((aByte >> 4) & 0xF, 16);\n                escaped.append(ch);\n                ch = Character.forDigit(aByte & 0xF, 16);\n                escaped.append(ch);\n            }\n\n            matcher.appendReplacement(sb, Matcher.quoteReplacement(escaped.toString().toLowerCase()));\n        }\n\n        matcher.appendTail(sb);\n        return sb.toString();\n    }\n\n    /**\n     * Merge all consecutive underscores and spaces into a single underscore, e.g. \"ab___c_  _d\" becomes \"ab_c_d\"\n     *\n     * @param s String to process\n     * @return The resulting string.\n     */\n    public static String mergeToSingleUnderscore(String s) {\n        StringBuffer buffer = new StringBuffer();\n        boolean inMerge = false;\n        for (int i = 0; i < s.length(); i++) {\n            char c = s.charAt(i);\n            if (c == ' ' || c == '_') {\n                if (!inMerge) {\n                    buffer.append('_');\n                }\n                inMerge = true;\n\n            } else {\n                inMerge = false;\n                buffer.append(c);\n            }\n        }\n\n        return buffer.toString();\n    }\n\n    /**\n     * Checks whether the String fits the template for a transformation variable -  $[a-zA-Z][a-zA-Z0-9]+\n     * e.g.  $a4, $Bd, $abcdef, etc\n     *\n     * @param s The string to test\n     * @return Whether it's a variable or not\n     */\n    public static boolean isVariable(String s) {\n        if (s == null ||\n                s.length() < 2 ||\n                !s.startsWith(\"$\") ||\n                !Character.isLetter(s.charAt(1))) {\n            return false;\n        }\n\n        // check that the rest of the string is comprised of letters and digits only:\n        for (int i = 2; i < s.length(); i++) {\n            char c = s.charAt(i);\n            if (!Character.isLetterOrDigit(c)) {\n                return false;\n            }\n        }\n\n        return true;\n    }\n\n    /**\n     * Replaces the char c in the string S, if it's the first character in the string.\n     * @param s The string to search\n     * @param c The character to replace\n     * @param replacement The string to replace the character in S\n     * @return The string with the character replaced (or the original string if the char is not found)\n     */\n    public static String replaceIfFirstChar(String s, char c, String replacement) {\n        return s.charAt(0) == c ? replacement + s.substring(1) : s;\n    }\n\n    /**\n     * Check if the given string starts with http:// or https://\n     * @param s The string to check\n     * @return Whether it's an http url or not\n     */\n    public static boolean isHttpUrl(String s) {\n        String lowerCaseSource = s.toLowerCase();\n        return lowerCaseSource.startsWith(\"https:/\") || lowerCaseSource.startsWith(\"http:/\");\n    }\n\n    /**\n     * Remove all consecutive chars c from the beginning of the string\n     * @param s String to process\n     * @param c Char to search for\n     * @return The string stripped from the starting chars.\n     */\n    public static String removeStartingChars(String s, char c) {\n        int lastToRemove = -1;\n        for (int i = 0; i < s.length(); i++) {\n            if (s.charAt(i) == c) {\n                lastToRemove = i;\n                continue;\n            }\n\n            if (s.charAt(i) != c) {\n                break;\n            }\n        }\n\n        if (lastToRemove < 0) return s;\n        return s.substring(lastToRemove + 1);\n    }\n\n    /**\n     * Checks whether a publicId starts a versioning string (v + number, e.g. v12345)\n     * @param publicId The url to check\n     * @return Whether a version string is contained within the publicId\n     */\n    public static boolean startWithVersionString(String publicId){\n        if (publicId.startsWith(\"/\")){\n            publicId = publicId.substring(1);\n        }\n        return publicId.length()>1 && publicId.startsWith(\"v\") && Character.isDigit(publicId.charAt(1));\n    }\n\n    /**\n     * Merges all occurrences of multiple slashes into a single slash (e.g. \"a///b//c/d\" becomes \"a/b/c/d\")\n     * @param url The string to process\n     * @return The resulting string with merged slashes.\n     */\n    public static String mergeSlashesInUrl(String url) {\n        StringBuilder builder = new StringBuilder();\n        boolean prevIsColon = false;\n        boolean inMerge = false;\n        for (int i = 0; i < url.length(); i++) {\n            char c = url.charAt(i);\n            if (c == ':') {\n                prevIsColon = true;\n                builder.append(c);\n            } else {\n                if (c == '/') {\n                    if (prevIsColon) {\n                        builder.append(c);\n                        inMerge = false;\n                    } else {\n                        if (!inMerge) {\n                            builder.append(c);\n                        }\n                        inMerge = true;\n                    }\n                } else {\n                    inMerge = false;\n                    builder.append(c);\n                }\n\n                prevIsColon = false;\n            }\n        }\n\n        return builder.toString();\n    }\n\n    /**\n     * Returns empty string value when passed string value is null or empty, the passed string itself otherwise.\n     *\n     * @param str string value to evaluate\n     * @return passed string value or empty string, if the passed string is null or empty\n     */\n    public static String emptyIfNull(String str) {\n        return isEmpty(str) ? \"\" : str;\n    }\n\n    /**\n     * Returns an array of strings in reveresed order.\n     *\n     * @param strings array of strings\n     * @return reversed array of string or empty array, if the passed array is null or empty\n     */\n    static String[] reverseStringArray(String[] strings) {\n        Collections.reverse(Arrays.asList(strings));\n        return strings;\n    }\n\n    /**\n     * Returns the padded string with requested character to the left with length equals to length param sent.\n     *\n     * @param inputString The string to process\n     * @param length The requested length to pad to\n     * @param paddingCharacter The requested character to pad with\n     * @return reversed array of string or empty array, if the passed array is null or empty\n     */\n    public static String padStart(String inputString, int length, char paddingCharacter) {\n        if (inputString.length() >= length) {\n            return inputString;\n        }\n        StringBuilder sb = new StringBuilder();\n        while (sb.length() < length - inputString.length()) {\n            sb.append(paddingCharacter);\n        }\n        sb.append(inputString);\n\n        return sb.toString();\n    }\n\n    /**\n     * Break string into groups of n size strings\n     *\n     * @param text The string to process\n     * @param n Size of group\n     * @return List with all strings with group size n.\n     */\n    public static List<String> getAllSubStringWithSize(String text, int n) {\n        List<String> results = new ArrayList<>();\n\n        Pattern pattern = Pattern.compile(\".{1,\" + n + \"}\");\n        Matcher matcher = pattern.matcher(text);\n        while (matcher.find()) {\n            String match = text.substring(matcher.start(), matcher.end());\n            results.add(match);\n        }\n        return results;\n    }\n}\n"
  },
  {
    "path": "cloudinary-core/src/main/java/org/cloudinary/json/JSONArray.java",
    "content": "package org.cloudinary.json;\n\n/*\n Copyright (c) 2002 JSON.org\n\n Permission is hereby granted, free of charge, to any person obtaining a copy\n of this software and associated documentation files (the \"Software\"), to deal\n in the Software without restriction, including without limitation the rights\n to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n copies of the Software, and to permit persons to whom the Software is\n furnished to do so, subject to the following conditions:\n\n The above copyright notice and this permission notice shall be included in all\n copies or substantial portions of the Software.\n\n The Software shall be used for Good, not Evil.\n\n THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n SOFTWARE.\n */\n\nimport java.io.IOException;\nimport java.io.Serializable;\nimport java.io.StringWriter;\nimport java.io.Writer;\nimport java.lang.reflect.Array;\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.Iterator;\nimport java.util.Map;\n\n/**\n * A JSONArray is an ordered sequence of values. Its external text form is a\n * string wrapped in square brackets with commas separating the values. The\n * internal form is an object having <code>get</code> and <code>opt</code>\n * methods for accessing the values by index, and <code>put</code> methods for\n * adding or replacing values. The values can be any of these types:\n * <code>Boolean</code>, <code>JSONArray</code>, <code>JSONObject</code>,\n * <code>Number</code>, <code>String</code>, or the\n * <code>JSONObject.NULL object</code>.\n * <p>\n * The constructor can convert a JSON text into a Java object. The\n * <code>toString</code> method converts to JSON text.\n * <p>\n * A <code>get</code> method returns a value if one can be found, and throws an\n * exception if one cannot be found. An <code>opt</code> method returns a\n * default value instead of throwing an exception, and so is useful for\n * obtaining optional values.\n * <p>\n * The generic <code>get()</code> and <code>opt()</code> methods return an\n * object which you can cast or query for type. There are also typed\n * <code>get</code> and <code>opt</code> methods that do type checking and type\n * coercion for you.\n * <p>\n * The texts produced by the <code>toString</code> methods strictly conform to\n * JSON syntax rules. The constructors are more forgiving in the texts they will\n * accept:\n * <ul>\n * <li>An extra <code>,</code>&nbsp;<small>(comma)</small> may appear just\n * before the closing bracket.</li>\n * <li>The <code>null</code> value will be inserted when there is <code>,</code>\n * &nbsp;<small>(comma)</small> elision.</li>\n * <li>Strings may be quoted with <code>'</code>&nbsp;<small>(single\n * quote)</small>.</li>\n * <li>Strings do not need to be quoted at all if they do not begin with a quote\n * or single quote, and if they do not contain leading or trailing spaces, and\n * if they do not contain any of these characters:\n * <code>{ } [ ] / \\ : , #</code> and if they do not look like numbers and if\n * they are not the reserved words <code>true</code>, <code>false</code>, or\n * <code>null</code>.</li>\n * </ul>\n *\n * @author JSON.org\n * @version 2014-05-03\n */\npublic class JSONArray implements Serializable {\n\n    /**\n     * The arrayList where the JSONArray's properties are kept.\n     */\n    private final ArrayList<Object> myArrayList;\n\n    /**\n     * Construct an empty JSONArray.\n     */\n    public JSONArray() {\n        this.myArrayList = new ArrayList<Object>();\n    }\n\n    /**\n     * Construct a JSONArray from a JSONTokener.\n     *\n     * @param x A JSONTokener\n     * @throws JSONException If there is a syntax error.\n     */\n    public JSONArray(JSONTokener x) throws JSONException {\n        this();\n        if (x.nextClean() != '[') {\n            throw x.syntaxError(\"A JSONArray text must start with '['\");\n        }\n        if (x.nextClean() != ']') {\n            x.back();\n            for (; ; ) {\n                if (x.nextClean() == ',') {\n                    x.back();\n                    this.myArrayList.add(JSONObject.NULL);\n                } else {\n                    x.back();\n                    this.myArrayList.add(x.nextValue());\n                }\n                switch (x.nextClean()) {\n                    case ',':\n                        if (x.nextClean() == ']') {\n                            return;\n                        }\n                        x.back();\n                        break;\n                    case ']':\n                        return;\n                    default:\n                        throw x.syntaxError(\"Expected a ',' or ']'\");\n                }\n            }\n        }\n    }\n\n    /**\n     * Construct a JSONArray from a source JSON text.\n     *\n     * @param source A string that begins with <code>[</code>&nbsp;<small>(left\n     *               bracket)</small> and ends with <code>]</code>\n     *               &nbsp;<small>(right bracket)</small>.\n     * @throws JSONException If there is a syntax error.\n     */\n    public JSONArray(String source) throws JSONException {\n        this(new JSONTokener(source));\n    }\n\n    /**\n     * Construct a JSONArray from a Collection.\n     *\n     * @param collection A Collection.\n     */\n    public JSONArray(Collection<Object> collection) {\n        this.myArrayList = new ArrayList<Object>();\n        if (collection != null) {\n            Iterator<Object> iter = collection.iterator();\n            while (iter.hasNext()) {\n                this.myArrayList.add(JSONObject.wrap(iter.next()));\n            }\n        }\n    }\n\n    /**\n     * Construct a JSONArray from an array\n     *\n     * @throws JSONException If not an array.\n     */\n    public JSONArray(Object array) throws JSONException {\n        this();\n        if (array.getClass().isArray()) {\n            int length = Array.getLength(array);\n            for (int i = 0; i < length; i += 1) {\n                this.put(JSONObject.wrap(Array.get(array, i)));\n            }\n        } else {\n            throw new JSONException(\"JSONArray initial value should be a string or collection or array.\");\n        }\n    }\n\n    /**\n     * Get the object value associated with an index.\n     *\n     * @param index The index must be between 0 and length() - 1.\n     * @return An object value.\n     * @throws JSONException If there is no value for the index.\n     */\n    public Object get(int index) throws JSONException {\n        Object object = this.opt(index);\n        if (object == null) {\n            throw new JSONException(\"JSONArray[\" + index + \"] not found.\");\n        }\n        return object;\n    }\n\n    /**\n     * Get the boolean value associated with an index. The string values \"true\"\n     * and \"false\" are converted to boolean.\n     *\n     * @param index The index must be between 0 and length() - 1.\n     * @return The truth.\n     * @throws JSONException If there is no value for the index or if the value is not\n     *                       convertible to boolean.\n     */\n    public boolean getBoolean(int index) throws JSONException {\n        Object object = this.get(index);\n        if (object.equals(Boolean.FALSE) || (object instanceof String && ((String) object).equalsIgnoreCase(\"false\"))) {\n            return false;\n        } else if (object.equals(Boolean.TRUE) || (object instanceof String && ((String) object).equalsIgnoreCase(\"true\"))) {\n            return true;\n        }\n        throw new JSONException(\"JSONArray[\" + index + \"] is not a boolean.\");\n    }\n\n    /**\n     * Get the double value associated with an index.\n     *\n     * @param index The index must be between 0 and length() - 1.\n     * @return The value.\n     * @throws JSONException If the key is not found or if the value cannot be converted\n     *                       to a number.\n     */\n    public double getDouble(int index) throws JSONException {\n        Object object = this.get(index);\n        try {\n            return object instanceof Number ? ((Number) object).doubleValue() : Double.parseDouble((String) object);\n        } catch (Exception e) {\n            throw new JSONException(\"JSONArray[\" + index + \"] is not a number.\");\n        }\n    }\n\n    /**\n     * Get the int value associated with an index.\n     *\n     * @param index The index must be between 0 and length() - 1.\n     * @return The value.\n     * @throws JSONException If the key is not found or if the value is not a number.\n     */\n    public int getInt(int index) throws JSONException {\n        Object object = this.get(index);\n        try {\n            return object instanceof Number ? ((Number) object).intValue() : Integer.parseInt((String) object);\n        } catch (Exception e) {\n            throw new JSONException(\"JSONArray[\" + index + \"] is not a number.\");\n        }\n    }\n\n    /**\n     * Get the JSONArray associated with an index.\n     *\n     * @param index The index must be between 0 and length() - 1.\n     * @return A JSONArray value.\n     * @throws JSONException If there is no value for the index. or if the value is not a\n     *                       JSONArray\n     */\n    public JSONArray getJSONArray(int index) throws JSONException {\n        Object object = this.get(index);\n        if (object instanceof JSONArray) {\n            return (JSONArray) object;\n        }\n        throw new JSONException(\"JSONArray[\" + index + \"] is not a JSONArray.\");\n    }\n\n    /**\n     * Get the JSONObject associated with an index.\n     *\n     * @param index subscript\n     * @return A JSONObject value.\n     * @throws JSONException If there is no value for the index or if the value is not a\n     *                       JSONObject\n     */\n    public JSONObject getJSONObject(int index) throws JSONException {\n        Object object = this.get(index);\n        if (object instanceof JSONObject) {\n            return (JSONObject) object;\n        }\n        throw new JSONException(\"JSONArray[\" + index + \"] is not a JSONObject.\");\n    }\n\n    /**\n     * Get the long value associated with an index.\n     *\n     * @param index The index must be between 0 and length() - 1.\n     * @return The value.\n     * @throws JSONException If the key is not found or if the value cannot be converted\n     *                       to a number.\n     */\n    public long getLong(int index) throws JSONException {\n        Object object = this.get(index);\n        try {\n            return object instanceof Number ? ((Number) object).longValue() : Long.parseLong((String) object);\n        } catch (Exception e) {\n            throw new JSONException(\"JSONArray[\" + index + \"] is not a number.\");\n        }\n    }\n\n    /**\n     * Get the string associated with an index.\n     *\n     * @param index The index must be between 0 and length() - 1.\n     * @return A string value.\n     * @throws JSONException If there is no string value for the index.\n     */\n    public String getString(int index) throws JSONException {\n        Object object = this.get(index);\n        if (object instanceof String) {\n            return (String) object;\n        }\n        throw new JSONException(\"JSONArray[\" + index + \"] not a string.\");\n    }\n\n    /**\n     * Determine if the value is null.\n     *\n     * @param index The index must be between 0 and length() - 1.\n     * @return true if the value at the index is null, or if there is no value.\n     */\n    public boolean isNull(int index) {\n        return JSONObject.NULL.equals(this.opt(index));\n    }\n\n    /**\n     * Make a string from the contents of this JSONArray. The\n     * <code>separator</code> string is inserted between each element. Warning:\n     * This method assumes that the data structure is acyclical.\n     *\n     * @param separator A string that will be inserted between the elements.\n     * @return a string.\n     * @throws JSONException If the array contains an invalid number.\n     */\n    public String join(String separator) throws JSONException {\n        int len = this.length();\n        StringBuilder sb = new StringBuilder();\n\n        for (int i = 0; i < len; i += 1) {\n            if (i > 0) {\n                sb.append(separator);\n            }\n            sb.append(JSONObject.valueToString(this.myArrayList.get(i)));\n        }\n        return sb.toString();\n    }\n\n    /**\n     * Get the number of elements in the JSONArray, included nulls.\n     *\n     * @return The length (or size).\n     */\n    public int length() {\n        return this.myArrayList.size();\n    }\n\n    /**\n     * Get the optional object value associated with an index.\n     *\n     * @param index The index must be between 0 and length() - 1.\n     * @return An object value, or null if there is no object at that index.\n     */\n    public Object opt(int index) {\n        return (index < 0 || index >= this.length()) ? null : this.myArrayList.get(index);\n    }\n\n    /**\n     * Get the optional boolean value associated with an index. It returns false\n     * if there is no value at that index, or if the value is not Boolean.TRUE\n     * or the String \"true\".\n     *\n     * @param index The index must be between 0 and length() - 1.\n     * @return The truth.\n     */\n    public boolean optBoolean(int index) {\n        return this.optBoolean(index, false);\n    }\n\n    /**\n     * Get the optional boolean value associated with an index. It returns the\n     * defaultValue if there is no value at that index or if it is not a Boolean\n     * or the String \"true\" or \"false\" (case insensitive).\n     *\n     * @param index        The index must be between 0 and length() - 1.\n     * @param defaultValue A boolean default.\n     * @return The truth.\n     */\n    public boolean optBoolean(int index, boolean defaultValue) {\n        try {\n            return this.getBoolean(index);\n        } catch (Exception e) {\n            return defaultValue;\n        }\n    }\n\n    /**\n     * Get the optional double value associated with an index. NaN is returned\n     * if there is no value for the index, or if the value is not a number and\n     * cannot be converted to a number.\n     *\n     * @param index The index must be between 0 and length() - 1.\n     * @return The value.\n     */\n    public double optDouble(int index) {\n        return this.optDouble(index, Double.NaN);\n    }\n\n    /**\n     * Get the optional double value associated with an index. The defaultValue\n     * is returned if there is no value for the index, or if the value is not a\n     * number and cannot be converted to a number.\n     *\n     * @param index        subscript\n     * @param defaultValue The default value.\n     * @return The value.\n     */\n    public double optDouble(int index, double defaultValue) {\n        try {\n            return this.getDouble(index);\n        } catch (Exception e) {\n            return defaultValue;\n        }\n    }\n\n    /**\n     * Get the optional int value associated with an index. Zero is returned if\n     * there is no value for the index, or if the value is not a number and\n     * cannot be converted to a number.\n     *\n     * @param index The index must be between 0 and length() - 1.\n     * @return The value.\n     */\n    public int optInt(int index) {\n        return this.optInt(index, 0);\n    }\n\n    /**\n     * Get the optional int value associated with an index. The defaultValue is\n     * returned if there is no value for the index, or if the value is not a\n     * number and cannot be converted to a number.\n     *\n     * @param index        The index must be between 0 and length() - 1.\n     * @param defaultValue The default value.\n     * @return The value.\n     */\n    public int optInt(int index, int defaultValue) {\n        try {\n            return this.getInt(index);\n        } catch (Exception e) {\n            return defaultValue;\n        }\n    }\n\n    /**\n     * Get the optional JSONArray associated with an index.\n     *\n     * @param index subscript\n     * @return A JSONArray value, or null if the index has no value, or if the\n     * value is not a JSONArray.\n     */\n    public JSONArray optJSONArray(int index) {\n        Object o = this.opt(index);\n        return o instanceof JSONArray ? (JSONArray) o : null;\n    }\n\n    /**\n     * Get the optional JSONObject associated with an index. Null is returned if\n     * the key is not found, or null if the index has no value, or if the value\n     * is not a JSONObject.\n     *\n     * @param index The index must be between 0 and length() - 1.\n     * @return A JSONObject value.\n     */\n    public JSONObject optJSONObject(int index) {\n        Object o = this.opt(index);\n        return o instanceof JSONObject ? (JSONObject) o : null;\n    }\n\n    /**\n     * Get the optional long value associated with an index. Zero is returned if\n     * there is no value for the index, or if the value is not a number and\n     * cannot be converted to a number.\n     *\n     * @param index The index must be between 0 and length() - 1.\n     * @return The value.\n     */\n    public long optLong(int index) {\n        return this.optLong(index, 0);\n    }\n\n    /**\n     * Get the optional long value associated with an index. The defaultValue is\n     * returned if there is no value for the index, or if the value is not a\n     * number and cannot be converted to a number.\n     *\n     * @param index        The index must be between 0 and length() - 1.\n     * @param defaultValue The default value.\n     * @return The value.\n     */\n    public long optLong(int index, long defaultValue) {\n        try {\n            return this.getLong(index);\n        } catch (Exception e) {\n            return defaultValue;\n        }\n    }\n\n    /**\n     * Get the optional string value associated with an index. It returns an\n     * empty string if there is no value at that index. If the value is not a\n     * string and is not null, then it is coverted to a string.\n     *\n     * @param index The index must be between 0 and length() - 1.\n     * @return A String value.\n     */\n    public String optString(int index) {\n        return this.optString(index, \"\");\n    }\n\n    /**\n     * Get the optional string associated with an index. The defaultValue is\n     * returned if the key is not found.\n     *\n     * @param index        The index must be between 0 and length() - 1.\n     * @param defaultValue The default value.\n     * @return A String value.\n     */\n    public String optString(int index, String defaultValue) {\n        Object object = this.opt(index);\n        return JSONObject.NULL.equals(object) ? defaultValue : object.toString();\n    }\n\n    /**\n     * Append a boolean value. This increases the array's length by one.\n     *\n     * @param value A boolean value.\n     * @return this.\n     */\n    public JSONArray put(boolean value) {\n        this.put(value ? Boolean.TRUE : Boolean.FALSE);\n        return this;\n    }\n\n    /**\n     * Put a value in the JSONArray, where the value will be a JSONArray which\n     * is produced from a Collection.\n     *\n     * @param value A Collection value.\n     * @return this.\n     */\n    public JSONArray put(Collection<Object> value) {\n        this.put(new JSONArray(value));\n        return this;\n    }\n\n    /**\n     * Append a double value. This increases the array's length by one.\n     *\n     * @param value A double value.\n     * @return this.\n     * @throws JSONException if the value is not finite.\n     */\n    public JSONArray put(double value) throws JSONException {\n        Double d = new Double(value);\n        JSONObject.testValidity(d);\n        this.put(d);\n        return this;\n    }\n\n    /**\n     * Append an int value. This increases the array's length by one.\n     *\n     * @param value An int value.\n     * @return this.\n     */\n    public JSONArray put(int value) {\n        this.put(new Integer(value));\n        return this;\n    }\n\n    /**\n     * Append an long value. This increases the array's length by one.\n     *\n     * @param value A long value.\n     * @return this.\n     */\n    public JSONArray put(long value) {\n        this.put(new Long(value));\n        return this;\n    }\n\n    /**\n     * Put a value in the JSONArray, where the value will be a JSONObject which\n     * is produced from a Map.\n     *\n     * @param value A Map value.\n     * @return this.\n     */\n    public JSONArray put(Map<String, Object> value) {\n        this.put(new JSONObject(value));\n        return this;\n    }\n\n    /**\n     * Append an object value. This increases the array's length by one.\n     *\n     * @param value An object value. The value should be a Boolean, Double,\n     *              Integer, JSONArray, JSONObject, Long, or String, or the\n     *              JSONObject.NULL object.\n     * @return this.\n     */\n    public JSONArray put(Object value) {\n        this.myArrayList.add(value);\n        return this;\n    }\n\n    /**\n     * Put or replace a boolean value in the JSONArray. If the index is greater\n     * than the length of the JSONArray, then null elements will be added as\n     * necessary to pad it out.\n     *\n     * @param index The subscript.\n     * @param value A boolean value.\n     * @return this.\n     * @throws JSONException If the index is negative.\n     */\n    public JSONArray put(int index, boolean value) throws JSONException {\n        this.put(index, value ? Boolean.TRUE : Boolean.FALSE);\n        return this;\n    }\n\n    /**\n     * Put a value in the JSONArray, where the value will be a JSONArray which\n     * is produced from a Collection.\n     *\n     * @param index The subscript.\n     * @param value A Collection value.\n     * @return this.\n     * @throws JSONException If the index is negative or if the value is not finite.\n     */\n    public JSONArray put(int index, Collection<Object> value) throws JSONException {\n        this.put(index, new JSONArray(value));\n        return this;\n    }\n\n    /**\n     * Put or replace a double value. If the index is greater than the length of\n     * the JSONArray, then null elements will be added as necessary to pad it\n     * out.\n     *\n     * @param index The subscript.\n     * @param value A double value.\n     * @return this.\n     * @throws JSONException If the index is negative or if the value is not finite.\n     */\n    public JSONArray put(int index, double value) throws JSONException {\n        this.put(index, new Double(value));\n        return this;\n    }\n\n    /**\n     * Put or replace an int value. If the index is greater than the length of\n     * the JSONArray, then null elements will be added as necessary to pad it\n     * out.\n     *\n     * @param index The subscript.\n     * @param value An int value.\n     * @return this.\n     * @throws JSONException If the index is negative.\n     */\n    public JSONArray put(int index, int value) throws JSONException {\n        this.put(index, new Integer(value));\n        return this;\n    }\n\n    /**\n     * Put or replace a long value. If the index is greater than the length of\n     * the JSONArray, then null elements will be added as necessary to pad it\n     * out.\n     *\n     * @param index The subscript.\n     * @param value A long value.\n     * @return this.\n     * @throws JSONException If the index is negative.\n     */\n    public JSONArray put(int index, long value) throws JSONException {\n        this.put(index, new Long(value));\n        return this;\n    }\n\n    /**\n     * Put a value in the JSONArray, where the value will be a JSONObject that\n     * is produced from a Map.\n     *\n     * @param index The subscript.\n     * @param value The Map value.\n     * @return this.\n     * @throws JSONException If the index is negative or if the the value is an invalid\n     *                       number.\n     */\n    public JSONArray put(int index, Map<String, Object> value) throws JSONException {\n        this.put(index, new JSONObject(value));\n        return this;\n    }\n\n    /**\n     * Put or replace an object value in the JSONArray. If the index is greater\n     * than the length of the JSONArray, then null elements will be added as\n     * necessary to pad it out.\n     *\n     * @param index The subscript.\n     * @param value The value to put into the array. The value should be a\n     *              Boolean, Double, Integer, JSONArray, JSONObject, Long, or\n     *              String, or the JSONObject.NULL object.\n     * @return this.\n     * @throws JSONException If the index is negative or if the the value is an invalid\n     *                       number.\n     */\n    public JSONArray put(int index, Object value) throws JSONException {\n        JSONObject.testValidity(value);\n        if (index < 0) {\n            throw new JSONException(\"JSONArray[\" + index + \"] not found.\");\n        }\n        if (index < this.length()) {\n            this.myArrayList.set(index, value);\n        } else {\n            while (index != this.length()) {\n                this.put(JSONObject.NULL);\n            }\n            this.put(value);\n        }\n        return this;\n    }\n\n    /**\n     * Remove an index and close the hole.\n     *\n     * @param index The index of the element to be removed.\n     * @return The value that was associated with the index, or null if there\n     * was no value.\n     */\n    public Object remove(int index) {\n        return index >= 0 && index < this.length() ? this.myArrayList.remove(index) : null;\n    }\n\n    /**\n     * Determine if two JSONArrays are similar. They must contain similar\n     * sequences.\n     *\n     * @param other The other JSONArray\n     * @return true if they are equal\n     */\n    public boolean similar(Object other) {\n        if (!(other instanceof JSONArray)) {\n            return false;\n        }\n        int len = this.length();\n        if (len != ((JSONArray) other).length()) {\n            return false;\n        }\n        for (int i = 0; i < len; i += 1) {\n            Object valueThis = this.get(i);\n            Object valueOther = ((JSONArray) other).get(i);\n            if (valueThis instanceof JSONObject) {\n                if (!((JSONObject) valueThis).similar(valueOther)) {\n                    return false;\n                }\n            } else if (valueThis instanceof JSONArray) {\n                if (!((JSONArray) valueThis).similar(valueOther)) {\n                    return false;\n                }\n            } else if (!valueThis.equals(valueOther)) {\n                return false;\n            }\n        }\n        return true;\n    }\n\n    /**\n     * Produce a JSONObject by combining a JSONArray of names with the values of\n     * this JSONArray.\n     *\n     * @param names A JSONArray containing a list of key strings. These will be\n     *              paired with the values.\n     * @return A JSONObject, or null if there are no names or if this JSONArray\n     * has no values.\n     * @throws JSONException If any of the names are null.\n     */\n    public JSONObject toJSONObject(JSONArray names) throws JSONException {\n        if (names == null || names.length() == 0 || this.length() == 0) {\n            return null;\n        }\n        JSONObject jo = new JSONObject();\n        for (int i = 0; i < names.length(); i += 1) {\n            jo.put(names.getString(i), this.opt(i));\n        }\n        return jo;\n    }\n\n    /**\n     * Make a JSON text of this JSONArray. For compactness, no unnecessary\n     * whitespace is added. If it is not possible to produce a syntactically\n     * correct JSON text then null will be returned instead. This could occur if\n     * the array contains an invalid number.\n     * <p>\n     * Warning: This method assumes that the data structure is acyclical.\n     *\n     * @return a printable, displayable, transmittable representation of the\n     * array.\n     */\n    public String toString() {\n        try {\n            return this.toString(0);\n        } catch (Exception e) {\n            return null;\n        }\n    }\n\n    /**\n     * Make a prettyprinted JSON text of this JSONArray. Warning: This method\n     * assumes that the data structure is acyclical.\n     *\n     * @param indentFactor The number of spaces to add to each level of indentation.\n     * @return a printable, displayable, transmittable representation of the\n     * object, beginning with <code>[</code>&nbsp;<small>(left\n     * bracket)</small> and ending with <code>]</code>\n     * &nbsp;<small>(right bracket)</small>.\n     * @throws JSONException\n     */\n    public String toString(int indentFactor) throws JSONException {\n        StringWriter sw = new StringWriter();\n        synchronized (sw.getBuffer()) {\n            return this.write(sw, indentFactor, 0).toString();\n        }\n    }\n\n    /**\n     * Write the contents of the JSONArray as JSON text to a writer. For\n     * compactness, no whitespace is added.\n     * <p>\n     * Warning: This method assumes that the data structure is acyclical.\n     *\n     * @return The writer.\n     * @throws JSONException\n     */\n    public Writer write(Writer writer) throws JSONException {\n        return this.write(writer, 0, 0);\n    }\n\n    /**\n     * Write the contents of the JSONArray as JSON text to a writer. For\n     * compactness, no whitespace is added.\n     * <p>\n     * Warning: This method assumes that the data structure is acyclical.\n     *\n     * @param indentFactor The number of spaces to add to each level of indentation.\n     * @param indent       The indention of the top level.\n     * @return The writer.\n     * @throws JSONException\n     */\n    Writer write(Writer writer, int indentFactor, int indent) throws JSONException {\n        try {\n            boolean commanate = false;\n            int length = this.length();\n            writer.write('[');\n\n            if (length == 1) {\n                JSONObject.writeValue(writer, this.myArrayList.get(0), indentFactor, indent);\n            } else if (length != 0) {\n                final int newindent = indent + indentFactor;\n\n                for (int i = 0; i < length; i += 1) {\n                    if (commanate) {\n                        writer.write(',');\n                    }\n                    if (indentFactor > 0) {\n                        writer.write('\\n');\n                    }\n                    JSONObject.indent(writer, newindent);\n                    JSONObject.writeValue(writer, this.myArrayList.get(i), indentFactor, newindent);\n                    commanate = true;\n                }\n                if (indentFactor > 0) {\n                    writer.write('\\n');\n                }\n                JSONObject.indent(writer, indent);\n            }\n            writer.write(']');\n            return writer;\n        } catch (IOException e) {\n            throw new JSONException(e);\n        }\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    public <T> ArrayList<T> toList(Class<T> type) {\n        ArrayList<T> listdata = new ArrayList<T>();\n        for (int i = 0; i < this.length(); i++) {\n            listdata.add((T) this.get(i));\n        }\n        return listdata;\n    }\n\n}\n"
  },
  {
    "path": "cloudinary-core/src/main/java/org/cloudinary/json/JSONException.java",
    "content": "package org.cloudinary.json;\n\n/**\n * The JSONException is thrown by the JSON.org classes when things are amiss.\n *\n * @author JSON.org\n * @version 2014-05-03\n */\npublic class JSONException extends RuntimeException {\n    private static final long serialVersionUID = 0;\n    private Throwable cause;\n\n    /**\n     * Constructs a JSONException with an explanatory message.\n     *\n     * @param message Detail about the reason for the exception.\n     */\n    public JSONException(String message) {\n        super(message);\n    }\n\n    /**\n     * Constructs a new JSONException with the specified cause.\n     *\n     * @param cause The cause.\n     */\n    public JSONException(Throwable cause) {\n        super(cause.getMessage());\n        this.cause = cause;\n    }\n\n    /**\n     * Returns the cause of this exception or null if the cause is nonexistent\n     * or unknown.\n     *\n     * @return the cause of this exception or null if the cause is nonexistent\n     * or unknown.\n     */\n    @Override\n    public Throwable getCause() {\n        return this.cause;\n    }\n}\n"
  },
  {
    "path": "cloudinary-core/src/main/java/org/cloudinary/json/JSONObject.java",
    "content": "package org.cloudinary.json;\n\n/*\n Copyright (c) 2002 JSON.org\n\n Permission is hereby granted, free of charge, to any person obtaining a copy\n of this software and associated documentation files (the \"Software\"), to deal\n in the Software without restriction, including without limitation the rights\n to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n copies of the Software, and to permit persons to whom the Software is\n furnished to do so, subject to the following conditions:\n\n The above copyright notice and this permission notice shall be included in all\n copies or substantial portions of the Software.\n\n The Software shall be used for Good, not Evil.\n\n THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n SOFTWARE.\n */\n\nimport java.io.IOException;\nimport java.io.Serializable;\nimport java.io.StringWriter;\nimport java.io.Writer;\nimport java.lang.reflect.Field;\nimport java.lang.reflect.Method;\nimport java.lang.reflect.Modifier;\nimport java.util.Collection;\nimport java.util.Enumeration;\nimport java.util.HashMap;\nimport java.util.Iterator;\nimport java.util.Locale;\nimport java.util.Map;\nimport java.util.Map.Entry;\nimport java.util.ResourceBundle;\nimport java.util.Set;\n\n/**\n * A JSONObject is an unordered collection of name/value pairs. Its external\n * form is a string wrapped in curly braces with colons between the names and\n * values, and commas between the values and names. The internal form is an\n * object having <code>get</code> and <code>opt</code> methods for accessing\n * the values by name, and <code>put</code> methods for adding or replacing\n * values by name. The values can be any of these types: <code>Boolean</code>,\n * <code>JSONArray</code>, <code>JSONObject</code>, <code>Number</code>,\n * <code>String</code>, or the <code>JSONObject.NULL</code> object. A\n * JSONObject constructor can be used to convert an external form JSON text\n * into an internal form whose values can be retrieved with the\n * <code>get</code> and <code>opt</code> methods, or to convert values into a\n * JSON text using the <code>put</code> and <code>toString</code> methods. A\n * <code>get</code> method returns a value if one can be found, and throws an\n * exception if one cannot be found. An <code>opt</code> method returns a\n * default value instead of throwing an exception, and so is useful for\n * obtaining optional values.\n * <p>\n * The generic <code>get()</code> and <code>opt()</code> methods return an\n * object, which you can cast or query for type. There are also typed\n * <code>get</code> and <code>opt</code> methods that do type checking and type\n * coercion for you. The opt methods differ from the get methods in that they\n * do not throw. Instead, they return a specified value, such as null.\n * <p>\n * The <code>put</code> methods add or replace values in an object. For\n * example,\n * <p>\n * <pre>\n * myString = new JSONObject()\n *         .put(&quot;JSON&quot;, &quot;Hello, World!&quot;).toString();\n * </pre>\n * <p>\n * produces the string <code>{\"JSON\": \"Hello, World\"}</code>.\n * <p>\n * The texts produced by the <code>toString</code> methods strictly conform to\n * the JSON syntax rules. The constructors are more forgiving in the texts they\n * will accept:\n * <ul>\n * <li>An extra <code>,</code>&nbsp;<small>(comma)</small> may appear just\n * before the closing brace.</li>\n * <li>Strings may be quoted with <code>'</code>&nbsp;<small>(single\n * quote)</small>.</li>\n * <li>Strings do not need to be quoted at all if they do not begin with a\n * quote or single quote, and if they do not contain leading or trailing\n * spaces, and if they do not contain any of these characters:\n * <code>{ } [ ] / \\ : , #</code> and if they do not look like numbers and\n * if they are not the reserved words <code>true</code>, <code>false</code>,\n * or <code>null</code>.</li>\n * </ul>\n *\n * @author JSON.org\n * @version 2014-05-03\n */\npublic class JSONObject implements Serializable{\n    /**\n     * JSONObject.NULL is equivalent to the value that JavaScript calls null,\n     * whilst Java's null is equivalent to the value that JavaScript calls\n     * undefined.\n     */\n    private static final class Null {\n\n        /**\n         * There is only intended to be a single instance of the NULL object,\n         * so the clone method returns itself.\n         *\n         * @return NULL.\n         */\n        @Override\n        protected final Object clone() {\n            return this;\n        }\n\n        /**\n         * A Null object is equal to the null value and to itself.\n         *\n         * @param object An object to test for nullness.\n         * @return true if the object parameter is the JSONObject.NULL object or\n         * null.\n         */\n        @Override\n        public boolean equals(Object object) {\n            return object == null || object == this;\n        }\n\n        /**\n         * Get the \"null\" string value.\n         *\n         * @return The string \"null\".\n         */\n        public String toString() {\n            return \"null\";\n        }\n    }\n\n    /**\n     * The map where the JSONObject's properties are kept.\n     */\n    private final Map<String, Object> map;\n\n    /**\n     * It is sometimes more convenient and less ambiguous to have a\n     * <code>NULL</code> object than to use Java's <code>null</code> value.\n     * <code>JSONObject.NULL.equals(null)</code> returns <code>true</code>.\n     * <code>JSONObject.NULL.toString()</code> returns <code>\"null\"</code>.\n     */\n    public static final Object NULL = new Null();\n\n    /**\n     * Construct an empty JSONObject.\n     */\n    public JSONObject() {\n        this.map = new HashMap<String, Object>();\n    }\n\n    /**\n     * Construct a JSONObject from a subset of another JSONObject. An array of\n     * strings is used to identify the keys that should be copied. Missing keys\n     * are ignored.\n     *\n     * @param jo    A JSONObject.\n     * @param names An array of strings.\n     * @throws JSONException\n     * @throws JSONException If a value is a non-finite number or if a name is\n     *                       duplicated.\n     */\n    public JSONObject(JSONObject jo, String[] names) {\n        this();\n        for (int i = 0; i < names.length; i += 1) {\n            try {\n                this.putOnce(names[i], jo.opt(names[i]));\n            } catch (Exception ignore) {\n            }\n        }\n    }\n\n    /**\n     * Construct a JSONObject from a JSONTokener.\n     *\n     * @param x A JSONTokener object containing the source string.\n     * @throws JSONException If there is a syntax error in the source string or a\n     *                       duplicated key.\n     */\n    public JSONObject(JSONTokener x) throws JSONException {\n        this();\n        char c;\n        String key;\n\n        if (x.nextClean() != '{') {\n            throw x.syntaxError(\"A JSONObject text must begin with '{'\");\n        }\n        for (; ; ) {\n            c = x.nextClean();\n            switch (c) {\n                case 0:\n                    throw x.syntaxError(\"A JSONObject text must end with '}'\");\n                case '}':\n                    return;\n                default:\n                    x.back();\n                    key = x.nextValue().toString();\n            }\n\n// The key is followed by ':'.\n\n            c = x.nextClean();\n            if (c != ':') {\n                throw x.syntaxError(\"Expected a ':' after a key\");\n            }\n            this.putOnce(key, x.nextValue());\n\n// Pairs are separated by ','.\n\n            switch (x.nextClean()) {\n                case ';':\n                case ',':\n                    if (x.nextClean() == '}') {\n                        return;\n                    }\n                    x.back();\n                    break;\n                case '}':\n                    return;\n                default:\n                    throw x.syntaxError(\"Expected a ',' or '}'\");\n            }\n        }\n    }\n\n    /**\n     * Construct a JSONObject from a Map.\n     *\n     * @param map A map object that can be used to initialize the contents of\n     *            the JSONObject.\n     * @throws JSONException\n     */\n    public JSONObject(Map<String, Object> map) {\n        this.map = new HashMap<String, Object>();\n        if (map != null) {\n            Iterator<Entry<String, Object>> i = map.entrySet().iterator();\n            while (i.hasNext()) {\n                Entry<String, Object> entry = i.next();\n                Object value = entry.getValue();\n                if (value != null) {\n                    this.map.put(entry.getKey(), wrap(value));\n                }\n            }\n        }\n    }\n\n    /**\n     * Construct a JSONObject from an Object using bean getters. It reflects on\n     * all of the public methods of the object. For each of the methods with no\n     * parameters and a name starting with <code>\"get\"</code> or\n     * <code>\"is\"</code> followed by an uppercase letter, the method is invoked,\n     * and a key and the value returned from the getter method are put into the\n     * new JSONObject.\n     * <p>\n     * The key is formed by removing the <code>\"get\"</code> or <code>\"is\"</code>\n     * prefix. If the second remaining character is not upper case, then the\n     * first character is converted to lower case.\n     * <p>\n     * For example, if an object has a method named <code>\"getName\"</code>, and\n     * if the result of calling <code>object.getName()</code> is\n     * <code>\"Larry Fine\"</code>, then the JSONObject will contain\n     * <code>\"name\": \"Larry Fine\"</code>.\n     *\n     * @param bean An object that has getter methods that should be used to make\n     *             a JSONObject.\n     */\n    public JSONObject(Object bean) {\n        this();\n        this.populateMap(bean);\n    }\n\n    /**\n     * Construct a JSONObject from an Object, using reflection to find the\n     * public members. The resulting JSONObject's keys will be the strings from\n     * the names array, and the values will be the field values associated with\n     * those keys in the object. If a key is not found or not visible, then it\n     * will not be copied into the new JSONObject.\n     *\n     * @param object An object that has fields that should be used to make a\n     *               JSONObject.\n     * @param names  An array of strings, the names of the fields to be obtained\n     *               from the object.\n     */\n    @SuppressWarnings(\"rawtypes\")\n    public JSONObject(Object object, String names[]) {\n        this();\n        Class c = object.getClass();\n        for (int i = 0; i < names.length; i += 1) {\n            String name = names[i];\n            try {\n                this.putOpt(name, c.getField(name).get(object));\n            } catch (Exception ignore) {\n            }\n        }\n    }\n\n    /**\n     * Construct a JSONObject from a source JSON text string. This is the most\n     * commonly used JSONObject constructor.\n     *\n     * @param source A string beginning with <code>{</code>&nbsp;<small>(left\n     *               brace)</small> and ending with <code>}</code>\n     *               &nbsp;<small>(right brace)</small>.\n     * @throws JSONException If there is a syntax error in the source string or a\n     *                       duplicated key.\n     */\n    public JSONObject(String source) throws JSONException {\n        this(new JSONTokener(source));\n    }\n\n    /**\n     * Construct a JSONObject from a ResourceBundle.\n     *\n     * @param baseName The ResourceBundle base name.\n     * @param locale   The Locale to load the ResourceBundle for.\n     * @throws JSONException If any JSONExceptions are detected.\n     */\n    public JSONObject(String baseName, Locale locale) throws JSONException {\n        this();\n        ResourceBundle bundle = ResourceBundle.getBundle(baseName, locale,\n                Thread.currentThread().getContextClassLoader());\n\n// Iterate through the keys in the bundle.\n\n        Enumeration<String> keys = bundle.getKeys();\n        while (keys.hasMoreElements()) {\n            Object key = keys.nextElement();\n            if (key != null) {\n\n// Go through the path, ensuring that there is a nested JSONObject for each\n// segment except the last. Add the value using the last segment's name into\n// the deepest nested JSONObject.\n\n                String[] path = ((String) key).split(\"\\\\.\");\n                int last = path.length - 1;\n                JSONObject target = this;\n                for (int i = 0; i < last; i += 1) {\n                    String segment = path[i];\n                    JSONObject nextTarget = target.optJSONObject(segment);\n                    if (nextTarget == null) {\n                        nextTarget = new JSONObject();\n                        target.put(segment, nextTarget);\n                    }\n                    target = nextTarget;\n                }\n                target.put(path[last], bundle.getString((String) key));\n            }\n        }\n    }\n\n    /**\n     * Accumulate values under a key. It is similar to the put method except\n     * that if there is already an object stored under the key then a JSONArray\n     * is stored under the key to hold all of the accumulated values. If there\n     * is already a JSONArray, then the new value is appended to it. In\n     * contrast, the put method replaces the previous value.\n     * <p>\n     * If only one value is accumulated that is not a JSONArray, then the result\n     * will be the same as using put. But if multiple values are accumulated,\n     * then the result will be like append.\n     *\n     * @param key   A key string.\n     * @param value An object to be accumulated under the key.\n     * @return this.\n     * @throws JSONException If the value is an invalid number or if the key is null.\n     */\n    public JSONObject accumulate(String key, Object value) throws JSONException {\n        testValidity(value);\n        Object object = this.opt(key);\n        if (object == null) {\n            this.put(key,\n                    value instanceof JSONArray ? new JSONArray().put(value)\n                            : value);\n        } else if (object instanceof JSONArray) {\n            ((JSONArray) object).put(value);\n        } else {\n            this.put(key, new JSONArray().put(object).put(value));\n        }\n        return this;\n    }\n\n    /**\n     * Append values to the array under a key. If the key does not exist in the\n     * JSONObject, then the key is put in the JSONObject with its value being a\n     * JSONArray containing the value parameter. If the key was already\n     * associated with a JSONArray, then the value parameter is appended to it.\n     *\n     * @param key   A key string.\n     * @param value An object to be accumulated under the key.\n     * @return this.\n     * @throws JSONException If the key is null or if the current value associated with\n     *                       the key is not a JSONArray.\n     */\n    public JSONObject append(String key, Object value) throws JSONException {\n        testValidity(value);\n        Object object = this.opt(key);\n        if (object == null) {\n            this.put(key, new JSONArray().put(value));\n        } else if (object instanceof JSONArray) {\n            this.put(key, ((JSONArray) object).put(value));\n        } else {\n            throw new JSONException(\"JSONObject[\" + key\n                    + \"] is not a JSONArray.\");\n        }\n        return this;\n    }\n\n    /**\n     * Produce a string from a double. The string \"null\" will be returned if the\n     * number is not finite.\n     *\n     * @param d A double.\n     * @return A String.\n     */\n    public static String doubleToString(double d) {\n        if (Double.isInfinite(d) || Double.isNaN(d)) {\n            return \"null\";\n        }\n\n// Shave off trailing zeros and decimal point, if possible.\n\n        String string = Double.toString(d);\n        if (string.indexOf('.') > 0 && string.indexOf('e') < 0\n                && string.indexOf('E') < 0) {\n            while (string.endsWith(\"0\")) {\n                string = string.substring(0, string.length() - 1);\n            }\n            if (string.endsWith(\".\")) {\n                string = string.substring(0, string.length() - 1);\n            }\n        }\n        return string;\n    }\n\n    /**\n     * Get the value object associated with a key.\n     *\n     * @param key A key string.\n     * @return The object associated with the key.\n     * @throws JSONException if the key is not found.\n     */\n    public Object get(String key) throws JSONException {\n        if (key == null) {\n            throw new JSONException(\"Null key.\");\n        }\n        Object object = this.opt(key);\n        if (object == null) {\n            throw new JSONException(\"JSONObject[\" + quote(key) + \"] not found.\");\n        }\n        return object;\n    }\n\n    /**\n     * Get the boolean value associated with a key.\n     *\n     * @param key A key string.\n     * @return The truth.\n     * @throws JSONException if the value is not a Boolean or the String \"true\" or\n     *                       \"false\".\n     */\n    public boolean getBoolean(String key) throws JSONException {\n        Object object = this.get(key);\n        if (object.equals(Boolean.FALSE)\n                || (object instanceof String && ((String) object)\n                .equalsIgnoreCase(\"false\"))) {\n            return false;\n        } else if (object.equals(Boolean.TRUE)\n                || (object instanceof String && ((String) object)\n                .equalsIgnoreCase(\"true\"))) {\n            return true;\n        }\n        throw new JSONException(\"JSONObject[\" + quote(key)\n                + \"] is not a Boolean.\");\n    }\n\n    /**\n     * Get the double value associated with a key.\n     *\n     * @param key A key string.\n     * @return The numeric value.\n     * @throws JSONException if the key is not found or if the value is not a Number\n     *                       object and cannot be converted to a number.\n     */\n    public double getDouble(String key) throws JSONException {\n        Object object = this.get(key);\n        try {\n            return object instanceof Number ? ((Number) object).doubleValue()\n                    : Double.parseDouble((String) object);\n        } catch (Exception e) {\n            throw new JSONException(\"JSONObject[\" + quote(key)\n                    + \"] is not a number.\");\n        }\n    }\n\n    /**\n     * Get the int value associated with a key.\n     *\n     * @param key A key string.\n     * @return The integer value.\n     * @throws JSONException if the key is not found or if the value cannot be converted\n     *                       to an integer.\n     */\n    public int getInt(String key) throws JSONException {\n        Object object = this.get(key);\n        try {\n            return object instanceof Number ? ((Number) object).intValue()\n                    : Integer.parseInt((String) object);\n        } catch (Exception e) {\n            throw new JSONException(\"JSONObject[\" + quote(key)\n                    + \"] is not an int.\");\n        }\n    }\n\n    /**\n     * Get the JSONArray value associated with a key.\n     *\n     * @param key A key string.\n     * @return A JSONArray which is the value.\n     * @throws JSONException if the key is not found or if the value is not a JSONArray.\n     */\n    public JSONArray getJSONArray(String key) throws JSONException {\n        Object object = this.get(key);\n        if (object instanceof JSONArray) {\n            return (JSONArray) object;\n        }\n        throw new JSONException(\"JSONObject[\" + quote(key)\n                + \"] is not a JSONArray.\");\n    }\n\n    /**\n     * Get the JSONObject value associated with a key.\n     *\n     * @param key A key string.\n     * @return A JSONObject which is the value.\n     * @throws JSONException if the key is not found or if the value is not a JSONObject.\n     */\n    public JSONObject getJSONObject(String key) throws JSONException {\n        Object object = this.get(key);\n        if (object instanceof JSONObject) {\n            return (JSONObject) object;\n        }\n        throw new JSONException(\"JSONObject[\" + quote(key)\n                + \"] is not a JSONObject.\");\n    }\n\n    /**\n     * Get the long value associated with a key.\n     *\n     * @param key A key string.\n     * @return The long value.\n     * @throws JSONException if the key is not found or if the value cannot be converted\n     *                       to a long.\n     */\n    public long getLong(String key) throws JSONException {\n        Object object = this.get(key);\n        try {\n            return object instanceof Number ? ((Number) object).longValue()\n                    : Long.parseLong((String) object);\n        } catch (Exception e) {\n            throw new JSONException(\"JSONObject[\" + quote(key)\n                    + \"] is not a long.\");\n        }\n    }\n\n    /**\n     * Get an array of field names from a JSONObject.\n     *\n     * @return An array of field names, or null if there are no names.\n     */\n    public static String[] getNames(JSONObject jo) {\n        int length = jo.length();\n        if (length == 0) {\n            return null;\n        }\n        Iterator<String> iterator = jo.keys();\n        String[] names = new String[length];\n        int i = 0;\n        while (iterator.hasNext()) {\n            names[i] = iterator.next();\n            i += 1;\n        }\n        return names;\n    }\n\n    /**\n     * Get an array of field names from an Object.\n     *\n     * @return An array of field names, or null if there are no names.\n     */\n    @SuppressWarnings(\"rawtypes\")\n    public static String[] getNames(Object object) {\n        if (object == null) {\n            return null;\n        }\n        Class klass = object.getClass();\n        Field[] fields = klass.getFields();\n        int length = fields.length;\n        if (length == 0) {\n            return null;\n        }\n        String[] names = new String[length];\n        for (int i = 0; i < length; i += 1) {\n            names[i] = fields[i].getName();\n        }\n        return names;\n    }\n\n    /**\n     * Get the string associated with a key.\n     *\n     * @param key A key string.\n     * @return A string which is the value.\n     * @throws JSONException if there is no string value for the key.\n     */\n    public String getString(String key) throws JSONException {\n        Object object = this.get(key);\n        if (object instanceof String) {\n            return (String) object;\n        }\n        throw new JSONException(\"JSONObject[\" + quote(key) + \"] not a string.\");\n    }\n\n    /**\n     * Determine if the JSONObject contains a specific key.\n     *\n     * @param key A key string.\n     * @return true if the key exists in the JSONObject.\n     */\n    public boolean has(String key) {\n        return this.map.containsKey(key);\n    }\n\n    /**\n     * Increment a property of a JSONObject. If there is no such property,\n     * create one with a value of 1. If there is such a property, and if it is\n     * an Integer, Long, Double, or Float, then add one to it.\n     *\n     * @param key A key string.\n     * @return this.\n     * @throws JSONException If there is already a property with this name that is not an\n     *                       Integer, Long, Double, or Float.\n     */\n    public JSONObject increment(String key) throws JSONException {\n        Object value = this.opt(key);\n        if (value == null) {\n            this.put(key, 1);\n        } else if (value instanceof Integer) {\n            this.put(key, (Integer) value + 1);\n        } else if (value instanceof Long) {\n            this.put(key, (Long) value + 1);\n        } else if (value instanceof Double) {\n            this.put(key, (Double) value + 1);\n        } else if (value instanceof Float) {\n            this.put(key, (Float) value + 1);\n        } else {\n            throw new JSONException(\"Unable to increment [\" + quote(key) + \"].\");\n        }\n        return this;\n    }\n\n    /**\n     * Determine if the value associated with the key is null or if there is no\n     * value.\n     *\n     * @param key A key string.\n     * @return true if there is no value associated with the key or if the value\n     * is the JSONObject.NULL object.\n     */\n    public boolean isNull(String key) {\n        return JSONObject.NULL.equals(this.opt(key));\n    }\n\n    /**\n     * Get an enumeration of the keys of the JSONObject.\n     *\n     * @return An iterator of the keys.\n     */\n    public Iterator<String> keys() {\n        return this.keySet().iterator();\n    }\n\n    /**\n     * Get a set of keys of the JSONObject.\n     *\n     * @return A keySet.\n     */\n    public Set<String> keySet() {\n        return this.map.keySet();\n    }\n\n    /**\n     * Get the number of keys stored in the JSONObject.\n     *\n     * @return The number of keys in the JSONObject.\n     */\n    public int length() {\n        return this.map.size();\n    }\n\n    /**\n     * Produce a JSONArray containing the names of the elements of this\n     * JSONObject.\n     *\n     * @return A JSONArray containing the key strings, or null if the JSONObject\n     * is empty.\n     */\n    public JSONArray names() {\n        JSONArray ja = new JSONArray();\n        Iterator<String> keys = this.keys();\n        while (keys.hasNext()) {\n            ja.put(keys.next());\n        }\n        return ja.length() == 0 ? null : ja;\n    }\n\n    /**\n     * Produce a string from a Number.\n     *\n     * @param number A Number\n     * @return A String.\n     * @throws JSONException If n is a non-finite number.\n     */\n    public static String numberToString(Number number) throws JSONException {\n        if (number == null) {\n            throw new JSONException(\"Null pointer\");\n        }\n        testValidity(number);\n\n// Shave off trailing zeros and decimal point, if possible.\n\n        String string = number.toString();\n        if (string.indexOf('.') > 0 && string.indexOf('e') < 0\n                && string.indexOf('E') < 0) {\n            while (string.endsWith(\"0\")) {\n                string = string.substring(0, string.length() - 1);\n            }\n            if (string.endsWith(\".\")) {\n                string = string.substring(0, string.length() - 1);\n            }\n        }\n        return string;\n    }\n\n    /**\n     * Get an optional value associated with a key.\n     *\n     * @param key A key string.\n     * @return An object which is the value, or null if there is no value.\n     */\n    public Object opt(String key) {\n        return key == null ? null : this.map.get(key);\n    }\n\n    /**\n     * Get an optional boolean associated with a key. It returns false if there\n     * is no such key, or if the value is not Boolean.TRUE or the String \"true\".\n     *\n     * @param key A key string.\n     * @return The truth.\n     */\n    public boolean optBoolean(String key) {\n        return this.optBoolean(key, false);\n    }\n\n    /**\n     * Get an optional boolean associated with a key. It returns the\n     * defaultValue if there is no such key, or if it is not a Boolean or the\n     * String \"true\" or \"false\" (case insensitive).\n     *\n     * @param key          A key string.\n     * @param defaultValue The default.\n     * @return The truth.\n     */\n    public boolean optBoolean(String key, boolean defaultValue) {\n        try {\n            return this.getBoolean(key);\n        } catch (Exception e) {\n            return defaultValue;\n        }\n    }\n\n    /**\n     * Get an optional double associated with a key, or NaN if there is no such\n     * key or if its value is not a number. If the value is a string, an attempt\n     * will be made to evaluate it as a number.\n     *\n     * @param key A string which is the key.\n     * @return An object which is the value.\n     */\n    public double optDouble(String key) {\n        return this.optDouble(key, Double.NaN);\n    }\n\n    /**\n     * Get an optional double associated with a key, or the defaultValue if\n     * there is no such key or if its value is not a number. If the value is a\n     * string, an attempt will be made to evaluate it as a number.\n     *\n     * @param key          A key string.\n     * @param defaultValue The default.\n     * @return An object which is the value.\n     */\n    public double optDouble(String key, double defaultValue) {\n        try {\n            return this.getDouble(key);\n        } catch (Exception e) {\n            return defaultValue;\n        }\n    }\n\n    /**\n     * Get an optional int value associated with a key, or zero if there is no\n     * such key or if the value is not a number. If the value is a string, an\n     * attempt will be made to evaluate it as a number.\n     *\n     * @param key A key string.\n     * @return An object which is the value.\n     */\n    public int optInt(String key) {\n        return this.optInt(key, 0);\n    }\n\n    /**\n     * Get an optional int value associated with a key, or the default if there\n     * is no such key or if the value is not a number. If the value is a string,\n     * an attempt will be made to evaluate it as a number.\n     *\n     * @param key          A key string.\n     * @param defaultValue The default.\n     * @return An object which is the value.\n     */\n    public int optInt(String key, int defaultValue) {\n        try {\n            return this.getInt(key);\n        } catch (Exception e) {\n            return defaultValue;\n        }\n    }\n\n    /**\n     * Get an optional JSONArray associated with a key. It returns null if there\n     * is no such key, or if its value is not a JSONArray.\n     *\n     * @param key A key string.\n     * @return A JSONArray which is the value.\n     */\n    public JSONArray optJSONArray(String key) {\n        Object o = this.opt(key);\n        return o instanceof JSONArray ? (JSONArray) o : null;\n    }\n\n    /**\n     * Get an optional JSONObject associated with a key. It returns null if\n     * there is no such key, or if its value is not a JSONObject.\n     *\n     * @param key A key string.\n     * @return A JSONObject which is the value.\n     */\n    public JSONObject optJSONObject(String key) {\n        Object object = this.opt(key);\n        return object instanceof JSONObject ? (JSONObject) object : null;\n    }\n\n    /**\n     * Get an optional long value associated with a key, or zero if there is no\n     * such key or if the value is not a number. If the value is a string, an\n     * attempt will be made to evaluate it as a number.\n     *\n     * @param key A key string.\n     * @return An object which is the value.\n     */\n    public long optLong(String key) {\n        return this.optLong(key, 0);\n    }\n\n    /**\n     * Get an optional long value associated with a key, or the default if there\n     * is no such key or if the value is not a number. If the value is a string,\n     * an attempt will be made to evaluate it as a number.\n     *\n     * @param key          A key string.\n     * @param defaultValue The default.\n     * @return An object which is the value.\n     */\n    public long optLong(String key, long defaultValue) {\n        try {\n            return this.getLong(key);\n        } catch (Exception e) {\n            return defaultValue;\n        }\n    }\n\n    /**\n     * Get an optional string associated with a key. It returns an empty string\n     * if there is no such key. If the value is not a string and is not null,\n     * then it is converted to a string.\n     *\n     * @param key A key string.\n     * @return A string which is the value.\n     */\n    public String optString(String key) {\n        return this.optString(key, \"\");\n    }\n\n    /**\n     * Get an optional string associated with a key. It returns the defaultValue\n     * if there is no such key.\n     *\n     * @param key          A key string.\n     * @param defaultValue The default.\n     * @return A string which is the value.\n     */\n    public String optString(String key, String defaultValue) {\n        Object object = this.opt(key);\n        return NULL.equals(object) ? defaultValue : object.toString();\n    }\n\n    @SuppressWarnings(\"rawtypes\")\n    private void populateMap(Object bean) {\n        Class klass = bean.getClass();\n\n// If klass is a System class then set includeSuperClass to false.\n\n        boolean includeSuperClass = klass.getClassLoader() != null;\n\n        Method[] methods = includeSuperClass ? klass.getMethods() : klass\n                .getDeclaredMethods();\n        for (int i = 0; i < methods.length; i += 1) {\n            try {\n                Method method = methods[i];\n                if (Modifier.isPublic(method.getModifiers())) {\n                    String name = method.getName();\n                    String key = \"\";\n                    if (name.startsWith(\"get\")) {\n                        if (\"getClass\".equals(name)\n                                || \"getDeclaringClass\".equals(name)) {\n                            key = \"\";\n                        } else {\n                            key = name.substring(3);\n                        }\n                    } else if (name.startsWith(\"is\")) {\n                        key = name.substring(2);\n                    }\n                    if (key.length() > 0\n                            && Character.isUpperCase(key.charAt(0))\n                            && method.getParameterTypes().length == 0) {\n                        if (key.length() == 1) {\n                            key = key.toLowerCase();\n                        } else if (!Character.isUpperCase(key.charAt(1))) {\n                            key = key.substring(0, 1).toLowerCase()\n                                    + key.substring(1);\n                        }\n\n                        Object result = method.invoke(bean, (Object[]) null);\n                        if (result != null) {\n                            this.map.put(key, wrap(result));\n                        }\n                    }\n                }\n            } catch (Exception ignore) {\n            }\n        }\n    }\n\n    /**\n     * Put a key/boolean pair in the JSONObject.\n     *\n     * @param key   A key string.\n     * @param value A boolean which is the value.\n     * @return this.\n     * @throws JSONException If the key is null.\n     */\n    public JSONObject put(String key, boolean value) throws JSONException {\n        this.put(key, value ? Boolean.TRUE : Boolean.FALSE);\n        return this;\n    }\n\n    /**\n     * Put a key/value pair in the JSONObject, where the value will be a\n     * JSONArray which is produced from a Collection.\n     *\n     * @param key   A key string.\n     * @param value A Collection value.\n     * @return this.\n     * @throws JSONException\n     */\n    public JSONObject put(String key, Collection<Object> value) throws JSONException {\n        this.put(key, new JSONArray(value));\n        return this;\n    }\n\n    /**\n     * Put a key/double pair in the JSONObject.\n     *\n     * @param key   A key string.\n     * @param value A double which is the value.\n     * @return this.\n     * @throws JSONException If the key is null or if the number is invalid.\n     */\n    public JSONObject put(String key, double value) throws JSONException {\n        this.put(key, new Double(value));\n        return this;\n    }\n\n    /**\n     * Put a key/int pair in the JSONObject.\n     *\n     * @param key   A key string.\n     * @param value An int which is the value.\n     * @return this.\n     * @throws JSONException If the key is null.\n     */\n    public JSONObject put(String key, int value) throws JSONException {\n        this.put(key, new Integer(value));\n        return this;\n    }\n\n    /**\n     * Put a key/long pair in the JSONObject.\n     *\n     * @param key   A key string.\n     * @param value A long which is the value.\n     * @return this.\n     * @throws JSONException If the key is null.\n     */\n    public JSONObject put(String key, long value) throws JSONException {\n        this.put(key, new Long(value));\n        return this;\n    }\n\n    /**\n     * Put a key/value pair in the JSONObject, where the value will be a\n     * JSONObject which is produced from a Map.\n     *\n     * @param key   A key string.\n     * @param value A Map value.\n     * @return this.\n     * @throws JSONException\n     */\n    public JSONObject put(String key, Map<String, Object> value) throws JSONException {\n        this.put(key, new JSONObject(value));\n        return this;\n    }\n\n    /**\n     * Put a key/value pair in the JSONObject. If the value is null, then the\n     * key will be removed from the JSONObject if it is present.\n     *\n     * @param key   A key string.\n     * @param value An object which is the value. It should be of one of these\n     *              types: Boolean, Double, Integer, JSONArray, JSONObject, Long,\n     *              String, or the JSONObject.NULL object.\n     * @return this.\n     * @throws JSONException If the value is non-finite number or if the key is null.\n     */\n    public JSONObject put(String key, Object value) throws JSONException {\n        if (key == null) {\n            throw new NullPointerException(\"Null key.\");\n        }\n        if (value != null) {\n            testValidity(value);\n            this.map.put(key, value);\n        } else {\n            this.remove(key);\n        }\n        return this;\n    }\n\n    /**\n     * Put a key/value pair in the JSONObject, but only if the key and the value\n     * are both non-null, and only if there is not already a member with that\n     * name.\n     *\n     * @param key   string\n     * @param value object\n     * @return this.\n     * @throws JSONException if the key is a duplicate\n     */\n    public JSONObject putOnce(String key, Object value) throws JSONException {\n        if (key != null && value != null) {\n            if (this.opt(key) != null) {\n                throw new JSONException(\"Duplicate key \\\"\" + key + \"\\\"\");\n            }\n            this.put(key, value);\n        }\n        return this;\n    }\n\n    /**\n     * Put a key/value pair in the JSONObject, but only if the key and the value\n     * are both non-null.\n     *\n     * @param key   A key string.\n     * @param value An object which is the value. It should be of one of these\n     *              types: Boolean, Double, Integer, JSONArray, JSONObject, Long,\n     *              String, or the JSONObject.NULL object.\n     * @return this.\n     * @throws JSONException If the value is a non-finite number.\n     */\n    public JSONObject putOpt(String key, Object value) throws JSONException {\n        if (key != null && value != null) {\n            this.put(key, value);\n        }\n        return this;\n    }\n\n    /**\n     * Produce a string in double quotes with backslash sequences in all the\n     * right places. A backslash will be inserted within &lt;/, producing &lt;\\/,\n     * allowing JSON text to be delivered in HTML. In JSON text, a string cannot\n     * contain a control character or an unescaped quote or backslash.\n     *\n     * @param string A String\n     * @return A String correctly formatted for insertion in a JSON text.\n     */\n    public static String quote(String string) {\n        StringWriter sw = new StringWriter();\n        synchronized (sw.getBuffer()) {\n            try {\n                return quote(string, sw).toString();\n            } catch (IOException ignored) {\n                // will never happen - we are writing to a string writer\n                return \"\";\n            }\n        }\n    }\n\n    public static Writer quote(String string, Writer w) throws IOException {\n        if (string == null || string.length() == 0) {\n            w.write(\"\\\"\\\"\");\n            return w;\n        }\n\n        char b;\n        char c = 0;\n        String hhhh;\n        int i;\n        int len = string.length();\n\n        w.write('\"');\n        for (i = 0; i < len; i += 1) {\n            b = c;\n            c = string.charAt(i);\n            switch (c) {\n                case '\\\\':\n                case '\"':\n                    w.write('\\\\');\n                    w.write(c);\n                    break;\n                case '/':\n                    if (b == '<') {\n                        w.write('\\\\');\n                    }\n                    w.write(c);\n                    break;\n                case '\\b':\n                    w.write(\"\\\\b\");\n                    break;\n                case '\\t':\n                    w.write(\"\\\\t\");\n                    break;\n                case '\\n':\n                    w.write(\"\\\\n\");\n                    break;\n                case '\\f':\n                    w.write(\"\\\\f\");\n                    break;\n                case '\\r':\n                    w.write(\"\\\\r\");\n                    break;\n                default:\n                    if (c < ' ' || (c >= '\\u0080' && c < '\\u00a0')\n                            || (c >= '\\u2000' && c < '\\u2100')) {\n                        w.write(\"\\\\u\");\n                        hhhh = Integer.toHexString(c);\n                        w.write(\"0000\", 0, 4 - hhhh.length());\n                        w.write(hhhh);\n                    } else {\n                        w.write(c);\n                    }\n            }\n        }\n        w.write('\"');\n        return w;\n    }\n\n    /**\n     * Remove a name and its value, if present.\n     *\n     * @param key The name to be removed.\n     * @return The value that was associated with the name, or null if there was\n     * no value.\n     */\n    public Object remove(String key) {\n        return this.map.remove(key);\n    }\n\n    /**\n     * Determine if two JSONObjects are similar.\n     * They must contain the same set of names which must be associated with\n     * similar values.\n     *\n     * @param other The other JSONObject\n     * @return true if they are equal\n     */\n    public boolean similar(Object other) {\n        try {\n            if (!(other instanceof JSONObject)) {\n                return false;\n            }\n            Set<String> set = this.keySet();\n            if (!set.equals(((JSONObject) other).keySet())) {\n                return false;\n            }\n            Iterator<String> iterator = set.iterator();\n            while (iterator.hasNext()) {\n                String name = iterator.next();\n                Object valueThis = this.get(name);\n                Object valueOther = ((JSONObject) other).get(name);\n                if (valueThis instanceof JSONObject) {\n                    if (!((JSONObject) valueThis).similar(valueOther)) {\n                        return false;\n                    }\n                } else if (valueThis instanceof JSONArray) {\n                    if (!((JSONArray) valueThis).similar(valueOther)) {\n                        return false;\n                    }\n                } else if (!valueThis.equals(valueOther)) {\n                    return false;\n                }\n            }\n            return true;\n        } catch (Throwable exception) {\n            return false;\n        }\n    }\n\n    /**\n     * Try to convert a string into a number, boolean, or null. If the string\n     * can't be converted, return the string.\n     *\n     * @param string A String.\n     * @return A simple JSON value.\n     */\n    public static Object stringToValue(String string) {\n        Double d;\n        if (string.equals(\"\")) {\n            return string;\n        }\n        if (string.equalsIgnoreCase(\"true\")) {\n            return Boolean.TRUE;\n        }\n        if (string.equalsIgnoreCase(\"false\")) {\n            return Boolean.FALSE;\n        }\n        if (string.equalsIgnoreCase(\"null\")) {\n            return JSONObject.NULL;\n        }\n\n        /*\n         * If it might be a number, try converting it. If a number cannot be\n         * produced, then the value will just be a string.\n         */\n\n        char b = string.charAt(0);\n        if ((b >= '0' && b <= '9') || b == '-') {\n            try {\n                if (string.indexOf('.') > -1 || string.indexOf('e') > -1\n                        || string.indexOf('E') > -1) {\n                    d = Double.valueOf(string);\n                    if (!d.isInfinite() && !d.isNaN()) {\n                        return d;\n                    }\n                } else {\n                    Long myLong = new Long(string);\n                    if (string.equals(myLong.toString())) {\n                        if (myLong == myLong.intValue()) {\n                            return myLong.intValue();\n                        } else {\n                            return myLong;\n                        }\n                    }\n                }\n            } catch (Exception ignore) {\n            }\n        }\n        return string;\n    }\n\n    /**\n     * Throw an exception if the object is a NaN or infinite number.\n     *\n     * @param o The object to test.\n     * @throws JSONException If o is a non-finite number.\n     */\n    public static void testValidity(Object o) throws JSONException {\n        if (o != null) {\n            if (o instanceof Double) {\n                if (((Double) o).isInfinite() || ((Double) o).isNaN()) {\n                    throw new JSONException(\n                            \"JSON does not allow non-finite numbers.\");\n                }\n            } else if (o instanceof Float) {\n                if (((Float) o).isInfinite() || ((Float) o).isNaN()) {\n                    throw new JSONException(\n                            \"JSON does not allow non-finite numbers.\");\n                }\n            }\n        }\n    }\n\n    /**\n     * Produce a JSONArray containing the values of the members of this\n     * JSONObject.\n     *\n     * @param names A JSONArray containing a list of key strings. This determines\n     *              the sequence of the values in the result.\n     * @return A JSONArray of values.\n     * @throws JSONException If any of the values are non-finite numbers.\n     */\n    public JSONArray toJSONArray(JSONArray names) throws JSONException {\n        if (names == null || names.length() == 0) {\n            return null;\n        }\n        JSONArray ja = new JSONArray();\n        for (int i = 0; i < names.length(); i += 1) {\n            ja.put(this.opt(names.getString(i)));\n        }\n        return ja;\n    }\n\n    /**\n     * Make a JSON text of this JSONObject. For compactness, no whitespace is\n     * added. If this would not result in a syntactically correct JSON text,\n     * then null will be returned instead.\n     * <p>\n     * Warning: This method assumes that the data structure is acyclical.\n     *\n     * @return a printable, displayable, portable, transmittable representation\n     * of the object, beginning with <code>{</code>&nbsp;<small>(left\n     * brace)</small> and ending with <code>}</code>&nbsp;<small>(right\n     * brace)</small>.\n     */\n    public String toString() {\n        try {\n            return this.toString(0);\n        } catch (Exception e) {\n            return null;\n        }\n    }\n\n    /**\n     * Make a prettyprinted JSON text of this JSONObject.\n     * <p>\n     * Warning: This method assumes that the data structure is acyclical.\n     *\n     * @param indentFactor The number of spaces to add to each level of indentation.\n     * @return a printable, displayable, portable, transmittable representation\n     * of the object, beginning with <code>{</code>&nbsp;<small>(left\n     * brace)</small> and ending with <code>}</code>&nbsp;<small>(right\n     * brace)</small>.\n     * @throws JSONException If the object contains an invalid number.\n     */\n    public String toString(int indentFactor) throws JSONException {\n        StringWriter w = new StringWriter();\n        synchronized (w.getBuffer()) {\n            return this.write(w, indentFactor, 0).toString();\n        }\n    }\n\n    /**\n     * Make a JSON text of an Object value. If the object has an\n     * value.toJSONString() method, then that method will be used to produce the\n     * JSON text. The method is required to produce a strictly conforming text.\n     * If the object does not contain a toJSONString method (which is the most\n     * common case), then a text will be produced by other means. If the value\n     * is an array or Collection, then a JSONArray will be made from it and its\n     * toJSONString method will be called. If the value is a MAP, then a\n     * JSONObject will be made from it and its toJSONString method will be\n     * called. Otherwise, the value's toString method will be called, and the\n     * result will be quoted.\n     * <p>\n     * <p>\n     * Warning: This method assumes that the data structure is acyclical.\n     *\n     * @param value The value to be serialized.\n     * @return a printable, displayable, transmittable representation of the\n     * object, beginning with <code>{</code>&nbsp;<small>(left\n     * brace)</small> and ending with <code>}</code>&nbsp;<small>(right\n     * brace)</small>.\n     * @throws JSONException If the value is or contains an invalid number.\n     */\n    @SuppressWarnings(\"unchecked\")\n    public static String valueToString(Object value) throws JSONException {\n        if (value == null || value.equals(null)) {\n            return \"null\";\n        }\n        if (value instanceof JSONString) {\n            Object object;\n            try {\n                object = ((JSONString) value).toJSONString();\n            } catch (Exception e) {\n                throw new JSONException(e);\n            }\n            if (object instanceof String) {\n                return (String) object;\n            }\n            throw new JSONException(\"Bad value from toJSONString: \" + object);\n        }\n        if (value instanceof Number) {\n            return numberToString((Number) value);\n        }\n        if (value instanceof Boolean || value instanceof JSONObject\n                || value instanceof JSONArray) {\n            return value.toString();\n        }\n        if (value instanceof Map) {\n            return new JSONObject((Map<String, Object>) value).toString();\n        }\n        if (value instanceof Collection) {\n            return new JSONArray((Collection<Object>) value).toString();\n        }\n        if (value.getClass().isArray()) {\n            return new JSONArray(value).toString();\n        }\n        return quote(value.toString());\n    }\n\n    /**\n     * Wrap an object, if necessary. If the object is null, return the NULL\n     * object. If it is an array or collection, wrap it in a JSONArray. If it is\n     * a map, wrap it in a JSONObject. If it is a standard property (Double,\n     * String, et al) then it is already wrapped. Otherwise, if it comes from\n     * one of the java packages, turn it into a string. And if it doesn't, try\n     * to wrap it in a JSONObject. If the wrapping fails, then null is returned.\n     *\n     * @param object The object to wrap\n     * @return The wrapped value\n     */\n    @SuppressWarnings(\"unchecked\")\n    public static Object wrap(Object object) {\n        try {\n            if (object == null) {\n                return NULL;\n            }\n            if (object instanceof JSONObject || object instanceof JSONArray\n                    || NULL.equals(object) || object instanceof JSONString\n                    || object instanceof Byte || object instanceof Character\n                    || object instanceof Short || object instanceof Integer\n                    || object instanceof Long || object instanceof Boolean\n                    || object instanceof Float || object instanceof Double\n                    || object instanceof String) {\n                return object;\n            }\n\n            if (object instanceof Collection) {\n                return new JSONArray((Collection<Object>) object);\n            }\n            if (object.getClass().isArray()) {\n                return new JSONArray(object);\n            }\n            if (object instanceof Map) {\n                return new JSONObject((Map<String, Object>) object);\n            }\n            Package objectPackage = object.getClass().getPackage();\n            String objectPackageName = objectPackage != null ? objectPackage\n                    .getName() : \"\";\n            if (objectPackageName.startsWith(\"java.\")\n                    || objectPackageName.startsWith(\"javax.\")\n                    || object.getClass().getClassLoader() == null) {\n                return object.toString();\n            }\n            return new JSONObject(object);\n        } catch (Exception exception) {\n            return null;\n        }\n    }\n\n    /**\n     * Write the contents of the JSONObject as JSON text to a writer. For\n     * compactness, no whitespace is added.\n     * <p>\n     * Warning: This method assumes that the data structure is acyclical.\n     *\n     * @return The writer.\n     * @throws JSONException\n     */\n    public Writer write(Writer writer) throws JSONException {\n        return this.write(writer, 0, 0);\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    static final Writer writeValue(Writer writer, Object value,\n                                   int indentFactor, int indent) throws JSONException, IOException {\n        if (value == null || value.equals(null)) {\n            writer.write(\"null\");\n        } else if (value instanceof JSONObject) {\n            ((JSONObject) value).write(writer, indentFactor, indent);\n        } else if (value instanceof JSONArray) {\n            ((JSONArray) value).write(writer, indentFactor, indent);\n        } else if (value instanceof Map) {\n            new JSONObject((Map<String, Object>) value).write(writer, indentFactor, indent);\n        } else if (value instanceof Collection) {\n            new JSONArray((Collection<Object>) value).write(writer, indentFactor,\n                    indent);\n        } else if (value.getClass().isArray()) {\n            new JSONArray(value).write(writer, indentFactor, indent);\n        } else if (value instanceof Number) {\n            writer.write(numberToString((Number) value));\n        } else if (value instanceof Boolean) {\n            writer.write(value.toString());\n        } else if (value instanceof JSONString) {\n            Object o;\n            try {\n                o = ((JSONString) value).toJSONString();\n            } catch (Exception e) {\n                throw new JSONException(e);\n            }\n            writer.write(o != null ? o.toString() : quote(value.toString()));\n        } else {\n            quote(value.toString(), writer);\n        }\n        return writer;\n    }\n\n    static final void indent(Writer writer, int indent) throws IOException {\n        for (int i = 0; i < indent; i += 1) {\n            writer.write(' ');\n        }\n    }\n\n    /**\n     * Write the contents of the JSONObject as JSON text to a writer. For\n     * compactness, no whitespace is added.\n     * <p>\n     * Warning: This method assumes that the data structure is acyclical.\n     *\n     * @return The writer.\n     * @throws JSONException\n     */\n    Writer write(Writer writer, int indentFactor, int indent)\n            throws JSONException {\n        try {\n            boolean commanate = false;\n            final int length = this.length();\n            Iterator<String> keys = this.keys();\n            writer.write('{');\n\n            if (length == 1) {\n                Object key = keys.next();\n                writer.write(quote(key.toString()));\n                writer.write(':');\n                if (indentFactor > 0) {\n                    writer.write(' ');\n                }\n                writeValue(writer, this.map.get(key), indentFactor, indent);\n            } else if (length != 0) {\n                final int newindent = indent + indentFactor;\n                while (keys.hasNext()) {\n                    Object key = keys.next();\n                    if (commanate) {\n                        writer.write(',');\n                    }\n                    if (indentFactor > 0) {\n                        writer.write('\\n');\n                    }\n                    indent(writer, newindent);\n                    writer.write(quote(key.toString()));\n                    writer.write(':');\n                    if (indentFactor > 0) {\n                        writer.write(' ');\n                    }\n                    writeValue(writer, this.map.get(key), indentFactor, newindent);\n                    commanate = true;\n                }\n                if (indentFactor > 0) {\n                    writer.write('\\n');\n                }\n                indent(writer, indent);\n            }\n            writer.write('}');\n            return writer;\n        } catch (IOException exception) {\n            throw new JSONException(exception);\n        }\n    }\n}\n"
  },
  {
    "path": "cloudinary-core/src/main/java/org/cloudinary/json/JSONString.java",
    "content": "package org.cloudinary.json;\n\n/**\n * The <code>JSONString</code> interface allows a <code>toJSONString()</code>\n * method so that a class can change the behavior of\n * <code>JSONObject.toString()</code>, <code>JSONArray.toString()</code>,\n * and <code>JSONWriter.value(</code>Object<code>)</code>. The\n * <code>toJSONString</code> method will be used instead of the default behavior\n * of using the Object's <code>toString()</code> method and quoting the result.\n */\npublic interface JSONString {\n    /**\n     * The <code>toJSONString</code> method allows a class to produce its own JSON\n     * serialization.\n     *\n     * @return A strictly syntactically correct JSON text.\n     */\n    public String toJSONString();\n}\n"
  },
  {
    "path": "cloudinary-core/src/main/java/org/cloudinary/json/JSONTokener.java",
    "content": "package org.cloudinary.json;\n\nimport java.io.BufferedReader;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.InputStreamReader;\nimport java.io.Reader;\nimport java.io.StringReader;\n\n/*\nCopyright (c) 2002 JSON.org\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nThe Software shall be used for Good, not Evil.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n*/\n\n/**\n * A JSONTokener takes a source string and extracts characters and tokens from\n * it. It is used by the JSONObject and JSONArray constructors to parse\n * JSON source strings.\n *\n * @author JSON.org\n * @version 2014-05-03\n */\npublic class JSONTokener {\n\n    private long character;\n    private boolean eof;\n    private long index;\n    private long line;\n    private char previous;\n    private Reader reader;\n    private boolean usePrevious;\n\n\n    /**\n     * Construct a JSONTokener from a Reader.\n     *\n     * @param reader A reader.\n     */\n    public JSONTokener(Reader reader) {\n        this.reader = reader.markSupported()\n                ? reader\n                : new BufferedReader(reader);\n        this.eof = false;\n        this.usePrevious = false;\n        this.previous = 0;\n        this.index = 0;\n        this.character = 1;\n        this.line = 1;\n    }\n\n\n    /**\n     * Construct a JSONTokener from an InputStream.\n     *\n     * @param inputStream The source.\n     */\n    public JSONTokener(InputStream inputStream) throws JSONException {\n        this(new InputStreamReader(inputStream));\n    }\n\n\n    /**\n     * Construct a JSONTokener from a string.\n     *\n     * @param s A source string.\n     */\n    public JSONTokener(String s) {\n        this(new StringReader(s));\n    }\n\n\n    /**\n     * Back up one character. This provides a sort of lookahead capability,\n     * so that you can test for a digit or letter before attempting to parse\n     * the next number or identifier.\n     */\n    public void back() throws JSONException {\n        if (this.usePrevious || this.index <= 0) {\n            throw new JSONException(\"Stepping back two steps is not supported\");\n        }\n        this.index -= 1;\n        this.character -= 1;\n        this.usePrevious = true;\n        this.eof = false;\n    }\n\n\n    /**\n     * Get the hex value of a character (base16).\n     *\n     * @param c A character between '0' and '9' or between 'A' and 'F' or\n     *          between 'a' and 'f'.\n     * @return An int between 0 and 15, or -1 if c was not a hex digit.\n     */\n    public static int dehexchar(char c) {\n        if (c >= '0' && c <= '9') {\n            return c - '0';\n        }\n        if (c >= 'A' && c <= 'F') {\n            return c - ('A' - 10);\n        }\n        if (c >= 'a' && c <= 'f') {\n            return c - ('a' - 10);\n        }\n        return -1;\n    }\n\n    public boolean end() {\n        return this.eof && !this.usePrevious;\n    }\n\n\n    /**\n     * Determine if the source string still contains characters that next()\n     * can consume.\n     *\n     * @return true if not yet at the end of the source.\n     */\n    public boolean more() throws JSONException {\n        this.next();\n        if (this.end()) {\n            return false;\n        }\n        this.back();\n        return true;\n    }\n\n\n    /**\n     * Get the next character in the source string.\n     *\n     * @return The next character, or 0 if past the end of the source string.\n     */\n    public char next() throws JSONException {\n        int c;\n        if (this.usePrevious) {\n            this.usePrevious = false;\n            c = this.previous;\n        } else {\n            try {\n                c = this.reader.read();\n            } catch (IOException exception) {\n                throw new JSONException(exception);\n            }\n\n            if (c <= 0) { // End of stream\n                this.eof = true;\n                c = 0;\n            }\n        }\n        this.index += 1;\n        if (this.previous == '\\r') {\n            this.line += 1;\n            this.character = c == '\\n' ? 0 : 1;\n        } else if (c == '\\n') {\n            this.line += 1;\n            this.character = 0;\n        } else {\n            this.character += 1;\n        }\n        this.previous = (char) c;\n        return this.previous;\n    }\n\n\n    /**\n     * Consume the next character, and check that it matches a specified\n     * character.\n     *\n     * @param c The character to match.\n     * @return The character.\n     * @throws JSONException if the character does not match.\n     */\n    public char next(char c) throws JSONException {\n        char n = this.next();\n        if (n != c) {\n            throw this.syntaxError(\"Expected '\" + c + \"' and instead saw '\" +\n                    n + \"'\");\n        }\n        return n;\n    }\n\n\n    /**\n     * Get the next n characters.\n     *\n     * @param n The number of characters to take.\n     * @return A string of n characters.\n     * @throws JSONException Substring bounds error if there are not\n     *                       n characters remaining in the source string.\n     */\n    public String next(int n) throws JSONException {\n        if (n == 0) {\n            return \"\";\n        }\n\n        char[] chars = new char[n];\n        int pos = 0;\n\n        while (pos < n) {\n            chars[pos] = this.next();\n            if (this.end()) {\n                throw this.syntaxError(\"Substring bounds error\");\n            }\n            pos += 1;\n        }\n        return new String(chars);\n    }\n\n\n    /**\n     * Get the next char in the string, skipping whitespace.\n     *\n     * @return A character, or 0 if there are no more characters.\n     * @throws JSONException\n     */\n    public char nextClean() throws JSONException {\n        for (; ; ) {\n            char c = this.next();\n            if (c == 0 || c > ' ') {\n                return c;\n            }\n        }\n    }\n\n\n    /**\n     * Return the characters up to the next close quote character.\n     * Backslash processing is done. The formal JSON format does not\n     * allow strings in single quotes, but an implementation is allowed to\n     * accept them.\n     *\n     * @param quote The quoting character, either\n     *              <code>\"</code>&nbsp;<small>(double quote)</small> or\n     *              <code>'</code>&nbsp;<small>(single quote)</small>.\n     * @return A String.\n     * @throws JSONException Unterminated string.\n     */\n    public String nextString(char quote) throws JSONException {\n        char c;\n        StringBuilder sb = new StringBuilder();\n        for (; ; ) {\n            c = this.next();\n            switch (c) {\n                case 0:\n                case '\\n':\n                case '\\r':\n                    throw this.syntaxError(\"Unterminated string\");\n                case '\\\\':\n                    c = this.next();\n                    switch (c) {\n                        case 'b':\n                            sb.append('\\b');\n                            break;\n                        case 't':\n                            sb.append('\\t');\n                            break;\n                        case 'n':\n                            sb.append('\\n');\n                            break;\n                        case 'f':\n                            sb.append('\\f');\n                            break;\n                        case 'r':\n                            sb.append('\\r');\n                            break;\n                        case 'u':\n                            sb.append((char) Integer.parseInt(this.next(4), 16));\n                            break;\n                        case '\"':\n                        case '\\'':\n                        case '\\\\':\n                        case '/':\n                            sb.append(c);\n                            break;\n                        default:\n                            throw this.syntaxError(\"Illegal escape.\");\n                    }\n                    break;\n                default:\n                    if (c == quote) {\n                        return sb.toString();\n                    }\n                    sb.append(c);\n            }\n        }\n    }\n\n\n    /**\n     * Get the text up but not including the specified character or the\n     * end of line, whichever comes first.\n     *\n     * @param delimiter A delimiter character.\n     * @return A string.\n     */\n    public String nextTo(char delimiter) throws JSONException {\n        StringBuilder sb = new StringBuilder();\n        for (; ; ) {\n            char c = this.next();\n            if (c == delimiter || c == 0 || c == '\\n' || c == '\\r') {\n                if (c != 0) {\n                    this.back();\n                }\n                return sb.toString().trim();\n            }\n            sb.append(c);\n        }\n    }\n\n\n    /**\n     * Get the text up but not including one of the specified delimiter\n     * characters or the end of line, whichever comes first.\n     *\n     * @param delimiters A set of delimiter characters.\n     * @return A string, trimmed.\n     */\n    public String nextTo(String delimiters) throws JSONException {\n        char c;\n        StringBuilder sb = new StringBuilder();\n        for (; ; ) {\n            c = this.next();\n            if (delimiters.indexOf(c) >= 0 || c == 0 ||\n                    c == '\\n' || c == '\\r') {\n                if (c != 0) {\n                    this.back();\n                }\n                return sb.toString().trim();\n            }\n            sb.append(c);\n        }\n    }\n\n\n    /**\n     * Get the next value. The value can be a Boolean, Double, Integer,\n     * JSONArray, JSONObject, Long, or String, or the JSONObject.NULL object.\n     *\n     * @return An object.\n     * @throws JSONException If syntax error.\n     */\n    public Object nextValue() throws JSONException {\n        char c = this.nextClean();\n        String string;\n\n        switch (c) {\n            case '\"':\n            case '\\'':\n                return this.nextString(c);\n            case '{':\n                this.back();\n                return new JSONObject(this);\n            case '[':\n                this.back();\n                return new JSONArray(this);\n        }\n\n        /*\n         * Handle unquoted text. This could be the values true, false, or\n         * null, or it can be a number. An implementation (such as this one)\n         * is allowed to also accept non-standard forms.\n         *\n         * Accumulate characters until we reach the end of the text or a\n         * formatting character.\n         */\n\n        StringBuilder sb = new StringBuilder();\n        while (c >= ' ' && \",:]}/\\\\\\\"[{;=#\".indexOf(c) < 0) {\n            sb.append(c);\n            c = this.next();\n        }\n        this.back();\n\n        string = sb.toString().trim();\n        if (\"\".equals(string)) {\n            throw this.syntaxError(\"Missing value\");\n        }\n        return JSONObject.stringToValue(string);\n    }\n\n\n    /**\n     * Skip characters until the next character is the requested character.\n     * If the requested character is not found, no characters are skipped.\n     *\n     * @param to A character to skip to.\n     * @return The requested character, or zero if the requested character\n     * is not found.\n     */\n    public char skipTo(char to) throws JSONException {\n        char c;\n        try {\n            long startIndex = this.index;\n            long startCharacter = this.character;\n            long startLine = this.line;\n            this.reader.mark(1000000);\n            do {\n                c = this.next();\n                if (c == 0) {\n                    this.reader.reset();\n                    this.index = startIndex;\n                    this.character = startCharacter;\n                    this.line = startLine;\n                    return c;\n                }\n            } while (c != to);\n        } catch (IOException exception) {\n            throw new JSONException(exception);\n        }\n        this.back();\n        return c;\n    }\n\n\n    /**\n     * Make a JSONException to signal a syntax error.\n     *\n     * @param message The error message.\n     * @return A JSONException object, suitable for throwing\n     */\n    public JSONException syntaxError(String message) {\n        return new JSONException(message + this.toString());\n    }\n\n\n    /**\n     * Make a printable string of this JSONTokener.\n     *\n     * @return \" at {index} [character {character} line {line}]\"\n     */\n    public String toString() {\n        return \" at \" + this.index + \" [character \" + this.character + \" line \" +\n                this.line + \"]\";\n    }\n}\n"
  },
  {
    "path": "cloudinary-core/src/test/java/com/cloudinary/AuthTokenTest.java",
    "content": "package com.cloudinary;\n\nimport com.cloudinary.utils.ObjectUtils;\n\nimport org.hamcrest.CoreMatchers;\nimport org.hamcrest.Matchers;\nimport org.junit.Assert;\nimport org.junit.Before;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.rules.TestName;\n\nimport java.util.Calendar;\nimport java.util.Collections;\nimport java.util.Map;\nimport java.util.TimeZone;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\nimport static org.junit.Assert.*;\n\npublic class AuthTokenTest {\n    public static final String KEY = \"00112233FF99\";\n    public static final String ALT_KEY = \"CCBB2233FF00\";\n    private Cloudinary cloudinary;\n\n    @Rule\n    public TestName currentTest = new TestName();\n\n    @Before\n    public void setUp() {\n        System.out.println(\"Running \" + this.getClass().getName() + \".\" + currentTest.getMethodName());\n        this.cloudinary = new Cloudinary(\"cloudinary://a:b@test123?load_strategies=false&analytics=false\");\n        final AuthToken authToken = new AuthToken(KEY).duration(300);\n        authToken.startTime(11111111); // start time is set for test purposes\n        cloudinary.config.authToken = authToken;\n        cloudinary.config.cloudName = \"test123\";\n\n    }\n\n    @Test\n    public void generateWithStartAndDuration() throws Exception {\n        AuthToken t = new AuthToken(KEY);\n        t.startTime(1111111111).acl(\"/image/*\").duration(300);\n        assertEquals(\"should generate an authorization token with startTime and duration\", \"__cld_token__=st=1111111111~exp=1111111411~acl=%2fimage%2f*~hmac=1751370bcc6cfe9e03f30dd1a9722ba0f2cdca283fa3e6df3342a00a7528cc51\", t.generate());\n    }\n\n    @Test\n    public void generateWithDuration() throws Exception {\n        long firstExp = Calendar.getInstance(TimeZone.getTimeZone(\"UTC\")).getTimeInMillis() / 1000L + 300;\n        Thread.sleep(1200);\n        String token = new AuthToken(KEY).acl(\"*\").duration(300).generate();\n        Thread.sleep(1200);\n        long secondExp = Calendar.getInstance(TimeZone.getTimeZone(\"UTC\")).getTimeInMillis() / 1000L + 300;\n        Matcher m = Pattern.compile(\"exp=(\\\\d+)\").matcher(token);\n        assertTrue(m.find());\n        final String expString = m.group(1);\n        final long actual = Long.parseLong(expString);\n        assertThat(actual, Matchers.greaterThanOrEqualTo(firstExp));\n        assertThat(actual, Matchers.lessThanOrEqualTo(secondExp));\n        assertEquals(token, new AuthToken(KEY).acl(\"*\").expiration(actual).generate());\n    }\n\n    @Test(expected = IllegalArgumentException.class)\n    public void testMustProvideExpirationOrDuration(){\n        new AuthToken(KEY).acl(\"*\").generate();\n    }\n\n    @Test\n    public void testAuthenticatedUrl() {\n        cloudinary.config.privateCdn = true;\n\n        String message = \"should add token if authToken is globally set and signed = true\";\n        String url = cloudinary.url().signed(true).resourceType(\"image\").type(\"authenticated\").version(\"1486020273\").generate(\"sample.jpg\");\n        assertEquals(message,\"https://test123-res.cloudinary.com/image/authenticated/v1486020273/sample.jpg?__cld_token__=st=11111111~exp=11111411~hmac=8db0d753ee7bbb9e2eaf8698ca3797436ba4c20e31f44527e43b6a6e995cfdb3\", url);\n\n        message = \"should add token for 'public' resource\";\n        url = cloudinary.url().signed(true).resourceType(\"image\").type(\"public\").version(\"1486020273\").generate(\"sample.jpg\");\n        assertEquals(message,\"https://test123-res.cloudinary.com/image/public/v1486020273/sample.jpg?__cld_token__=st=11111111~exp=11111411~hmac=c2b77d9f81be6d89b5d0ebc67b671557e88a40bcf03dd4a6997ff4b994ceb80e\", url);\n\n        message = \"should not add token if signed is false\";\n        url = cloudinary.url().resourceType(\"image\").type(\"authenticated\").version(\"1486020273\").generate(\"sample.jpg\");\n        assertEquals(message,\"https://test123-res.cloudinary.com/image/authenticated/v1486020273/sample.jpg\", url);\n\n        message = \"should not add token if authToken is globally set but null auth token is explicitly set and signed = true\";\n        url = cloudinary.url().authToken(AuthToken.NULL_AUTH_TOKEN).signed(true).resourceType(\"image\").type(\"authenticated\").version(\"1486020273\").generate(\"sample.jpg\");\n        assertEquals(message,\"https://test123-res.cloudinary.com/image/authenticated/s--v2fTPYTu--/v1486020273/sample.jpg\", url);\n\n        message = \"explicit authToken should override global setting\";\n        url = cloudinary.url().signed(true).authToken(new AuthToken(ALT_KEY).startTime(222222222).duration(100)).resourceType(\"image\").type(\"authenticated\").transformation(new Transformation().crop(\"scale\").width(300)).generate(\"sample.jpg\");\n        assertEquals(message,\"https://test123-res.cloudinary.com/image/authenticated/c_scale,w_300/sample.jpg?__cld_token__=st=222222222~exp=222222322~hmac=55cfe516530461213fe3b3606014533b1eca8ff60aeab79d1bb84c9322eebc1f\", url);\n\n        message = \"should compute expiration as start time + duration\";\n        url = cloudinary.url().signed(true).authToken(new AuthToken().startTime(11111111).duration(300))\n                .type(\"authenticated\").version(\"1486020273\").generate(\"sample.jpg\");\n        assertEquals(message,\"https://test123-res.cloudinary.com/image/authenticated/v1486020273/sample.jpg?__cld_token__=st=11111111~exp=11111411~hmac=8db0d753ee7bbb9e2eaf8698ca3797436ba4c20e31f44527e43b6a6e995cfdb3\", url);\n\n    }\n\n    @Test\n    public void testConfiguration() {\n        cloudinary = new Cloudinary(\"cloudinary://a:b@test123?load_strategies=false&auth_token[key]=aabbcc112233&auth_token[duration]=200\");\n        assertEquals(cloudinary.config.authToken, new AuthToken(\"aabbcc112233\").duration(200));\n    }\n\n    @Test\n    public void testTokenGeneration(){\n        AuthToken token = new AuthToken(KEY);\n        token.duration(300);\n        String user = \"foobar\"; // username taken from elsewhere\n        token.acl(\"/*/t_\" + user);\n        token.startTime(222222222); // we can't rely on the default \"now\" value in tests\n        String cookieToken = token.generate();\n        assertEquals(\"__cld_token__=st=222222222~exp=222222522~acl=%2f*%2ft_foobar~hmac=8e39600cc18cec339b21fe2b05fcb64b98de373355f8ce732c35710d8b10259f\", cookieToken);\n    }\n\n    @Test\n    public void testUrlInTag() {\n        String message = \"should add token to an image tag url\";\n        String url = cloudinary.url().signed(true).resourceType(\"image\").type(\"authenticated\").version(\"1486020273\").imageTag(\"sample.jpg\");\n        assertThat(url, Matchers.matchesPattern(\"<img.*src='https://res.cloudinary.com/test123/image/authenticated/v1486020273/sample.jpg\\\\?__cld_token__=st=11111111~exp=11111411~hmac=9bd6f41e2a5893da8343dc8eb648de8bf73771993a6d1457d49851250caf3b80.*>\"));\n\n    }\n\n    @Test\n    public void testIgnoreUrlIfAclIsProvided() {\n        String user = \"foobar\"; // username taken from elsewhere\n        AuthToken token = new AuthToken(KEY).duration(300).acl(\"/*/t_\" + user).startTime(222222222);\n        String cookieToken = token.generate();\n        AuthToken aclToken = new AuthToken(KEY).duration(300).acl(\"/*/t_\" + user).startTime(222222222);\n        String cookieAclToken = aclToken.generate(\"http://res.cloudinary.com/test123/image/upload/v1486020273/sample.jpg\");\n        assertEquals(cookieToken, cookieAclToken);\n    }\n\n    @Test\n    public void testMultiplePatternsInAcl() {\n        AuthToken token = new AuthToken(KEY).duration(3600).acl(\"/image/authenticated/*\", \"/image2/authenticated/*\", \"/image3/authenticated/*\").startTime(22222222);\n        String cookieToken = token.generate();\n        Assert.assertThat(cookieToken, CoreMatchers.containsString(\"~acl=%2fimage%2fauthenticated%2f*!%2fimage2%2fauthenticated%2f*!%2fimage3%2fauthenticated%2f*~\"));\n    }\n\n    @Test\n    public void testPublicAclInitializationFromMap() {\n        Map options = ObjectUtils.asMap(\n                \"acl\", Collections.singleton(\"foo\"),\n                \"expiration\", 100,\n                \"key\", KEY,\n                \"tokenName\", \"token\");\n        String token = new AuthToken(options).generate();\n        assertEquals(\"token=exp=100~acl=foo~hmac=88be250f3a912add862959076ee74f392fa0959a953fddd9128787d5c849efd9\", token);\n    }\n\n    @Test(expected = IllegalArgumentException.class)\n    public void testMissingAclAndUrlShouldThrow() {\n        String token = new AuthToken(KEY).duration(300).generate();\n    }\n\n    @Test\n    public void testMissingUrlNotMissingAclShouldNotThrow() {\n        String token = new AuthToken(KEY).duration(300).generate(\"http://res.cloudinary.com/test123\");\n    }\n\n\n}\n"
  },
  {
    "path": "cloudinary-core/src/test/java/com/cloudinary/TransformationTest.java",
    "content": "package com.cloudinary;\n\nimport com.cloudinary.transformation.Condition;\nimport com.cloudinary.transformation.TextLayer;\nimport com.cloudinary.utils.ObjectUtils;\nimport org.cloudinary.json.JSONArray;\nimport org.hamcrest.CoreMatchers;\nimport org.junit.After;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\nimport junitparams.JUnitParamsRunner;\nimport junitparams.Parameters;\n\nimport java.util.*;\n\nimport static com.cloudinary.transformation.Expression.faceCount;\nimport static com.cloudinary.transformation.Expression.variable;\nimport static org.junit.Assert.*;\n\n/**\n *\n */\n@SuppressWarnings(\"unchecked\")\n@RunWith(JUnitParamsRunner.class)\npublic class TransformationTest {\n\n    @Before\n    public void setUp() throws Exception {\n\n    }\n\n    @After\n    public void tearDown() throws Exception {\n\n    }\n\n    @Test\n    public void withLiteral() throws Exception {\n        Transformation transformation = new Transformation().ifCondition(\"w_lt_200\").crop(\"fill\").height(120).width(80);\n        assertEquals(\"should include the if parameter as the first component in the transformation string\", \"if_w_lt_200,c_fill,h_120,w_80\", transformation.toString());\n\n        transformation = new Transformation().crop(\"fill\").height(120).ifCondition(\"w_lt_200\").width(80);\n        assertEquals(\"should include the if parameter as the first component in the transformation string\", \"if_w_lt_200,c_fill,h_120,w_80\", transformation.toString());\n\n        String chained = \"[{if: \\\"w_lt_200\\\",crop: \\\"fill\\\",height: 120, width: 80}, {if: \\\"w_gt_400\\\",crop: \\\"fit\\\",width: 150,height: 150},{effect: \\\"sepia\\\"}]\";\n        List transformations = ObjectUtils.toList(new JSONArray(chained));\n\n        transformation = new Transformation(transformations);\n        assertEquals(\"should allow multiple conditions when chaining transformations\", \"if_w_lt_200,c_fill,h_120,w_80/if_w_gt_400,c_fit,h_150,w_150/e_sepia\", transformation.toString());\n    }\n\n    @Test\n    public void literalWithSpaces() throws Exception {\n        Map map = ObjectUtils.asMap(\"if\", \"width < 200\", \"crop\", \"fill\", \"height\", 120, \"width\", 80);\n        List<Map> list = new ArrayList<Map>();\n        list.add(map);\n        Transformation transformation = new Transformation(list);\n\n        assertEquals(\"should translate operators\", \"if_w_lt_200,c_fill,h_120,w_80\", transformation.toString());\n    }\n\n    @Test\n    public void endIf() throws Exception {\n        String chained = \"[{if: \\\"w_lt_200\\\"},\\n\" +\n                \"          {crop: \\\"fill\\\", height: 120, width: 80,effect: \\\"sharpen\\\"},\\n\" +\n                \"          {effect: \\\"brightness:50\\\"},\\n\" +\n                \"          {effect: \\\"shadow\\\",color: \\\"red\\\"}, {if: \\\"end\\\"}]\";\n        List transformations = ObjectUtils.toList(new JSONArray(chained));\n\n        Transformation transformation = new Transformation(transformations);\n        assertEquals(\"should include the if_end as the last parameter in its component\", \"if_w_lt_200/c_fill,e_sharpen,h_120,w_80/e_brightness:50/co_red,e_shadow/if_end\", transformation.toString());\n\n    }\n\n    @Test\n    public void ifElse() throws Exception {\n        String chained = \"[{if: \\\"w_lt_200\\\",crop: \\\"fill\\\",height: 120,width: 80},\\n\" +\n                \"          {if: \\\"else\\\",crop: \\\"fill\\\",height: 90, width: 100}]\";\n        List transformations = ObjectUtils.toList(new JSONArray(chained));\n\n        Transformation transformation = new Transformation(transformations);\n\n        assertEquals(\"should support if_else with transformation parameters\", \"if_w_lt_200,c_fill,h_120,w_80/if_else,c_fill,h_90,w_100\", transformation.toString());\n\n        chained = \"[{if: \\\"w_lt_200\\\"},\\n\" +\n                \"          {crop: \\\"fill\\\",height: 120,width: 80},\\n\" +\n                \"          {if: \\\"else\\\"},\\n\" +\n                \"          {crop: \\\"fill\\\",height: 90,width: 100}]\";\n        transformations = ObjectUtils.toList(new JSONArray(chained));\n\n        transformation = new Transformation(transformations);\n        assertEquals(\"if_else should be without any transformation parameters\", \"if_w_lt_200/c_fill,h_120,w_80/if_else/c_fill,h_90,w_100\", transformation.toString());\n    }\n\n    @Test\n    public void testDuration() throws Exception {\n        Transformation transformation = new Transformation().ifCondition().duration(\"gt\", \"30\").then().width(100).crop(\"scale\");\n        assertEquals(\"passing an operator and a value adds a condition\", \"if_du_gt_30,c_scale,w_100\", transformation.toString());\n        transformation = new Transformation().ifCondition().initialDuration(\"gt\", \"30\").then().width(100).crop(\"scale\");\n        assertEquals(\"passing an operator and a value adds a condition\", \"if_idu_gt_30,c_scale,w_100\", transformation.toString());\n        transformation=new Transformation().ifCondition(\"initialDuration > 20\").crop(\"scale\").width(200);\n        assertEquals(\"if_idu_gt_20,c_scale,w_200\", transformation.generate());\n    }\n\n\n    @Test\n    public void chainedConditions() throws Exception {\n        Transformation transformation = new Transformation().ifCondition().aspectRatio(\"gt\", \"3:4\").then().width(100).crop(\"scale\");\n        assertEquals(\"passing an operator and a value adds a condition\", \"if_ar_gt_3:4,c_scale,w_100\", transformation.toString());\n        transformation = new Transformation().ifCondition().aspectRatio(\"gt\", \"3:4\").and().width(\"gt\", 100).then().width(50).crop(\"scale\");\n        assertEquals(\"should chaining condition with `and`\", \"if_ar_gt_3:4_and_w_gt_100,c_scale,w_50\", transformation.toString());\n        transformation = new Transformation().ifCondition().aspectRatio(\"gt\", \"3:4\").and().width(\"gt\", 100).or().width(\"gt\", 200).then().width(50).crop(\"scale\");\n        assertEquals(\"should chain conditions with `or`\", \"if_ar_gt_3:4_and_w_gt_100_or_w_gt_200,c_scale,w_50\", transformation.toString());\n        transformation = new Transformation().ifCondition().aspectRatio(\">\", \"3:4\").and().width(\"<=\", 100).or().width(\"gt\", 200).then().width(50).crop(\"scale\");\n        assertEquals(\"should translate operators\", \"if_ar_gt_3:4_and_w_lte_100_or_w_gt_200,c_scale,w_50\", transformation.toString());\n        transformation = new Transformation().ifCondition().aspectRatio(\">\", \"3:4\").and().width(\"<=\", 100).or().width(\">\", 200).then().width(50).crop(\"scale\");\n        assertEquals(\"should translate operators\", \"if_ar_gt_3:4_and_w_lte_100_or_w_gt_200,c_scale,w_50\", transformation.toString());\n        transformation = new Transformation().ifCondition().aspectRatio(\">=\", \"3:4\").and().pageCount(\">=\", 100).or().pageCount(\"!=\", 0).then().width(50).crop(\"scale\");\n        assertEquals(\"should translate operators\", \"if_ar_gte_3:4_and_pc_gte_100_or_pc_ne_0,c_scale,w_50\", transformation.toString());\n\n    }\n\n    @Test\n    public void shouldSupportAndTranslateOperators() {\n\n        String allOperators =\n                        \"if_\"           +\n                        \"w_eq_0_and\"    +\n                        \"_h_ne_0_or\"    +\n                        \"_ar_lt_0_and\"   +\n                        \"_pc_gt_0_and\"   +\n                        \"_fc_lte_0_and\"  +\n                        \"_w_gte_0\"      +\n                        \",e_grayscale\";\n        assertEquals(\"should support and translate operators:  '=', '!=', '<', '>', '<=', '>=', '&&', '||'\",\n                allOperators, new Transformation().ifCondition()\n                        .width(\"=\", 0).and()\n                        .height(\"!=\", 0).or()\n                        .aspectRatio(\"<\", 0).and()\n                        .pageCount(\">\", 0).and()\n                        .faceCount(\"<=\", 0).and()\n                        .width(\">=\", 0)\n                        .then().effect(\"grayscale\").toString());\n\n        assertEquals(allOperators, new Transformation().ifCondition(\"w = 0 && height != 0 || aspectRatio < 0 and pageCount > 0 and faceCount <= 0 and width >= 0\")\n                .effect(\"grayscale\")\n                .toString());\n    }\n\n    @Test\n    public void endIf2() throws Exception {\n        Transformation transformation = new Transformation().ifCondition().width(\"gt\", 100).and().width(\"lt\", 200).then().width(50).crop(\"scale\").endIf();\n        assertEquals(\"should serialize to 'if_end'\", \"if_w_gt_100_and_w_lt_200/c_scale,w_50/if_end\", transformation.toString());\n        transformation = new Transformation().ifCondition().width(\"gt\", 100).and().width(\"lt\", 200).then().width(50).crop(\"scale\").endIf();\n        assertEquals(\"force the if clause to be chained\", \"if_w_gt_100_and_w_lt_200/c_scale,w_50/if_end\", transformation.toString());\n        transformation = new Transformation().ifCondition().width(\"gt\", 100).and().width(\"lt\", 200).then().width(50).crop(\"scale\").ifElse().width(100).crop(\"crop\").endIf();\n        assertEquals(\"force the if_else clause to be chained\", \"if_w_gt_100_and_w_lt_200/c_scale,w_50/if_else/c_crop,w_100/if_end\", transformation.toString());\n\n    }\n\n    @Test\n    public void testArrayShouldDefineASetOfVariables() {\n        // using methods\n        Transformation t = new Transformation();\n        t.ifCondition(\"face_count > 2\")\n                .variables(variable(\"$z\", 5), variable(\"$foo\", \"$z * 2\"))\n                .crop(\"scale\")\n                .width(\"$foo * 200\");\n        assertEquals(\"if_fc_gt_2,$z_5,$foo_$z_mul_2,c_scale,w_$foo_mul_200\", t.generate());\n    }\n\n    @Test\n    public void testShouldSortDefinedVariable(){\n        Transformation t = new Transformation().variable(\"$second\", 1).variable(\"$first\", 2);\n        assertEquals(\"$first_2,$second_1\", t.generate());\n    }\n\n    @Test\n    public void testShouldPlaceDefinedVariablesBeforeOrdered(){\n        Transformation t = new Transformation()\n                .variables(variable(\"$z\", 5), variable(\"$foo\", \"$z * 2\"))\n                .variable(\"$second\", 1)\n                .variable(\"$first\", 2);\n        assertEquals(\"$first_2,$second_1,$z_5,$foo_$z_mul_2\", t.generate());\n    }\n\n    @Test\n    public void testVariable(){\n        // using strings\n        Transformation t = new Transformation();\n        t.variable(\"$foo\", 10)\n                .chain()\n                .ifCondition(faceCount().gt(2))\n                .crop(\"scale\")\n                .width(new Condition(\"$foo * 200 / faceCount\"))\n                .endIf();\n        assertEquals(\"$foo_10/if_fc_gt_2/c_scale,w_$foo_mul_200_div_fc/if_end\", t.generate());\n    }\n\n    @Test\n    public void testShouldSupportTextValues() {\n        Transformation t = new Transformation();\n        t.effect(\"$efname\", 100)\n            .variable(\"$efname\", \"!blur!\");\n        assertEquals(\"$efname_!blur!,e_$efname:100\", t.generate());\n    }\n\n    @Test\n    public void testSupportStringInterpolation() {\n        Transformation t = new Transformation()\n                .crop(\"scale\")\n                .overlay(new TextLayer().text(\n                        \"$(start)Hello $(name)$(ext), $(no ) $( no)$(end)\"\n                ).fontFamily(\"Arial\").fontSize(18));\n        assertEquals(\"c_scale,l_text:Arial_18:$(start)Hello%20$(name)$(ext)%252C%20%24%28no%20%29%20%24%28%20no%29$(end)\", t.generate());\n    }\n\n    @Test\n    public void testShouldSupportPowOperator() {\n        Transformation t = new Transformation()\n                .variables(variable(\"$small\", 150), variable(\"$big\", \"$small ^ 1.5\"));\n\n        assertEquals(\"$small_150,$big_$small_pow_1.5\", t.generate());\n    }\n\n    @Test\n    public void testShouldNotChangeVariableNamesWhenTheyNamedAfterKeyword() {\n        Transformation t = new Transformation()\n                .variable(\"$width\", 10)\n                .chain()\n                .width(\"$width + 10 + width\");\n\n        assertEquals(\"$width_10/w_$width_add_10_add_w\", t.generate());\n    }\n\n    @Test\n    public void testRadiusTwoCornersAsValues() {\n        Transformation t = new Transformation()\n                .radius(10, 20);\n\n        assertEquals(\"r_10:20\", t.generate());\n    }\n\n    @Test\n    public void testRadiusTwoCornersAsExpressions() {\n        Transformation t = new Transformation()\n                .radius(\"10\", \"$v\");\n\n        assertEquals(\"r_10:$v\", t.generate());\n    }\n\n    @Test\n    public void testRadiusThreeCorners() {\n        Transformation t = new Transformation()\n                .radius(10, \"$v\", \"30\");\n\n        assertEquals(\"r_10:$v:30\", t.generate());\n    }\n\n    @Test\n    public void testRadiusFourCorners() {\n        Transformation t = new Transformation()\n                .radius(10, \"$v\", \"30\", 40);\n\n        assertEquals(\"r_10:$v:30:40\", t.generate());\n    }\n\n    @Test\n    public void testRadiusArray1() {\n        Transformation t = new Transformation()\n                .radius(new Object[]{10});\n\n        assertEquals(\"r_10\", t.generate());\n    }\n\n    @Test\n    public void testRadiusArray2() {\n        Transformation t = new Transformation()\n                .radius(new Object[]{10, \"$v\"});\n\n        assertEquals(\"r_10:$v\", t.generate());\n    }\n\n    @Test\n    public void testUserVariableNamesContainingPredefinedNamesAreNotAffected() {\n        Transformation t = new Transformation()\n                .variable(\"$mywidth\", \"100\")\n                .variable(\"$aheight\", 300)\n                .chain()\n                .width(\"3 + $mywidth * 3 + 4 / 2 * initialWidth * $mywidth\")\n                .height(\"3 * initialHeight + $aheight\");\n\n        assertEquals(\"$aheight_300,$mywidth_100/h_3_mul_ih_add_$aheight,w_3_add_$mywidth_mul_3_add_4_div_2_mul_iw_mul_$mywidth\", t.generate());\n    }\n\n    @Test\n    public void testContextMetadataToUserVariables() {\n        Transformation t = new Transformation()\n                .variable(\"$xpos\", \"ctx:!x_pos!_to_f\")\n                .variable(\"$ypos\", \"ctx:!y_pos!_to_f\")\n                .crop(\"crop\")\n                .x(\"$xpos * w\")\n                .y(\"$ypos * h\");\n\n        assertEquals(\"$xpos_ctx:!x_pos!_to_f,$ypos_ctx:!y_pos!_to_f,c_crop,x_$xpos_mul_w,y_$ypos_mul_h\", t.generate());\n    }\n\n    @Test\n    public void testFormatInTransformation() {\n        String t = new EagerTransformation().width(100).format(\"jpeg\").generate();\n        assertEquals(\"w_100/jpeg\", t);\n\n        t = new EagerTransformation().width(100).format(\"\").generate();\n        assertEquals(\"w_100/\", t);\n    }\n\n    @Parameters({ \"angle\",\n            \"aspect_ratio\",\n            \"dpr\",\n            \"effect\",\n            \"height\",\n            \"opacity\",\n            \"quality\",\n            \"width\",\n            \"x\",\n            \"y\",\n            \"end_offset\",\n            \"start_offset\",\n            \"zoom\" })\n    @Test\n    public void testVerifyNormalizationShouldNormalize(String input) throws Exception {\n        String t = new Transformation().param(input, \"width * 2\").generate();\n        assertThat(t, CoreMatchers.containsString(\"w_mul_2\"));\n    }\n\n    @Parameters({\n             \"audio_codec\",\n             \"audio_frequency\",\n             \"border\",\n             \"bit_rate\",\n             \"color_space\",\n             \"default_image\",\n             \"delay\",\n             \"density\",\n             \"fetch_format\",\n             \"custom_function\",\n             \"fps\",\n             \"gravity\",\n             \"overlay\",\n             \"prefix\",\n             \"page\",\n             \"underlay\",\n             \"video_sampling\",\n             \"streaming_profile\",\n             \"keyframe_interval\"})\n    @Test\n    public void testVerifyNormalizationShouldNotNormalize(String input) throws Exception {\n        String t = new Transformation().param(input, \"width * 2\").generate();\n        assertThat(t, CoreMatchers.not(CoreMatchers.containsString(\"w_mul_2\")));\n    }\n\n    @Test\n    public void testSupportStartOffset() throws Exception {\n        String t = new Transformation().width(100).startOffset(\"idu - 5\").generate();\n        assertThat(t, CoreMatchers.containsString(\"so_idu_sub_5\"));\n\n        t = new Transformation().width(100).startOffset(\"$logotime\").generate();\n        assertThat(t, CoreMatchers.containsString(\"so_$logotime\"));\n    }\n\n    @Test\n    public void testSupportEndOffset() throws Exception {\n        String t = new Transformation().width(100).endOffset(\"idu - 5\").generate();\n        assertThat(t, CoreMatchers.containsString(\"eo_idu_sub_5\"));\n\n        t = new Transformation().width(100).endOffset(\"$logotime\").generate();\n        assertThat(t, CoreMatchers.containsString(\"eo_$logotime\"));\n    }\n}\n"
  },
  {
    "path": "cloudinary-core/src/test/java/com/cloudinary/UtilTest.java",
    "content": "package com.cloudinary;\n\nimport com.cloudinary.utils.ObjectUtils;\nimport com.cloudinary.utils.StringUtils;\nimport org.cloudinary.json.JSONObject;\nimport org.junit.Test;\n\nimport java.text.ParseException;\nimport java.text.SimpleDateFormat;\nimport java.util.Date;\nimport java.util.Map;\n\nimport static org.junit.Assert.*;\n\n/**\n * Created by amir on 17/11/2016.\n */\npublic class UtilTest {\n\n    public static final String START = \"2019-02-22 16:20:57 +0200\";\n    public static final String END = \"2019-03-22 00:00:00 +0200\";\n    public static final String START_REFORMATTED = \"2019-02-22T14:20:57Z\";\n    public static final String END_REFORMATTED = \"2019-03-21T22:00:00Z\";\n\n    @Test\n    public void encodeContext() throws Exception {\n        Map context = ObjectUtils.asMap(\"caption\", \"different = caption\", \"alt2\", \"alt|alternative\");\n        String result = Util.encodeContext(context);\n        assertTrue(\"caption=different \\\\= caption|alt2=alt\\\\|alternative\".equals(result) ||\n                \"alt2=alt\\\\|alternative|caption=different \\\\= caption\".equals(result));\n    }\n\n    @Test\n    public void testAccessControlRule() throws ParseException {\n        SimpleDateFormat simpleDateFormat = new SimpleDateFormat(\"yyyy-MM-dd HH:mm:ss Z\");\n        final Date start = simpleDateFormat.parse(START);\n        final Date end = simpleDateFormat.parse(END);\n        AccessControlRule acl = AccessControlRule.anonymous(start, end);\n\n        JSONObject deserializedAcl = new JSONObject(acl.toString());\n        assertEquals(deserializedAcl.get(\"access_type\"), \"anonymous\");\n        assertEquals(deserializedAcl.get(\"start\"), START_REFORMATTED);\n        assertEquals(deserializedAcl.get(\"end\"), END_REFORMATTED);\n\n        acl = AccessControlRule.anonymousFrom(start);\n        assertEquals(2, acl.length());\n        assertEquals(deserializedAcl.get(\"access_type\"), \"anonymous\");\n        assertEquals(deserializedAcl.get(\"start\"), START_REFORMATTED);\n\n        acl = AccessControlRule.anonymousUntil(end);\n        assertEquals(2, acl.length());\n        assertEquals(deserializedAcl.get(\"access_type\"), \"anonymous\");\n        assertEquals(deserializedAcl.get(\"end\"), END_REFORMATTED);\n\n        AccessControlRule token = AccessControlRule.token();\n        assertEquals(1, token.length());\n        assertEquals(\"{\\\"access_type\\\":\\\"token\\\"}\", token.toString());\n\n    }\n\n    @Test\n    public void testMergeToSingleUnderscore() {\n        assertEquals(\"a_b_c_d\", StringUtils.mergeToSingleUnderscore(\"a_b_c_d\"));\n        assertEquals(\"a_b_c_d\", StringUtils.mergeToSingleUnderscore(\"a_b_c_    d\"));\n        assertEquals(\"a_b_c_d\", StringUtils.mergeToSingleUnderscore(\"a_b_c_  _  d\"));\n        assertEquals(\"_a_b_c_d_\", StringUtils.mergeToSingleUnderscore(\"___ _ a____   b_c_    d   _  _\"));\n        assertEquals(\"a\", StringUtils.mergeToSingleUnderscore(\"a\"));\n        assertEquals(\"a_\", StringUtils.mergeToSingleUnderscore(\"a___________\"));\n        assertEquals(\"_a\", StringUtils.mergeToSingleUnderscore(\"    a\"));\n    }\n\n    @Test\n    public void testIsVariable(){\n        assertTrue(StringUtils.isVariable(\"$a6\"));\n        assertTrue(StringUtils.isVariable(\"$a64534534\"));\n        assertTrue(StringUtils.isVariable(\"$ab\"));\n        assertTrue(StringUtils.isVariable(\"$asdasda\"));\n        assertTrue(StringUtils.isVariable(\"$a34asd12e\"));\n        assertTrue(StringUtils.isVariable(\"$a\"));\n\n        assertFalse(StringUtils.isVariable(\"sda\"));\n        assertFalse(StringUtils.isVariable(\"   \"));\n        assertFalse(StringUtils.isVariable(\"... . /\"));\n        assertFalse(StringUtils.isVariable(\"$\"));\n        assertFalse(StringUtils.isVariable(\"$4\"));\n        assertFalse(StringUtils.isVariable(\"$4dfds\"));\n        assertFalse(StringUtils.isVariable(\"$612s\"));\n        assertFalse(StringUtils.isVariable(\"$6 12s\"));\n        assertFalse(StringUtils.isVariable(\"$6 1.2s\"));\n    }\n\n    @Test\n    public void testReplaceIfFirstChar(){\n        assertEquals(\"abcdef\", StringUtils.replaceIfFirstChar(\"abcdef\", 'b', \"*\"));\n        assertEquals(\"abcdef\", StringUtils.replaceIfFirstChar(\"abcdef\", 'f', \"*\"));\n        assertEquals(\"abcdef\", StringUtils.replaceIfFirstChar(\"abcdef\", 'z', \"*\"));\n        assertEquals(\"abcdef\", StringUtils.replaceIfFirstChar(\"abcdef\", '4', \"*\"));\n        assertEquals(\"abcdef\", StringUtils.replaceIfFirstChar(\"abcdef\", '$', \"*\"));\n        assertEquals(\"abc#def\", StringUtils.replaceIfFirstChar(\"abc#def\", 'b', \"*\"));\n        assertEquals(\"$%^bcdef\", StringUtils.replaceIfFirstChar(\"$%^bcdef\", 'b', \"*\"));\n\n        assertEquals(\"*bcdef\", StringUtils.replaceIfFirstChar(\"abcdef\", 'a', \"*\"));\n        assertEquals(\"***bcdef\", StringUtils.replaceIfFirstChar(\"abcdef\", 'a', \"***\"));\n        assertEquals(\"aaabcdef\", StringUtils.replaceIfFirstChar(\"abcdef\", 'a', \"aaa\"));\n        assertEquals(\"---%^bcdef\", StringUtils.replaceIfFirstChar(\"$%^bcdef\", '$', \"---\"));\n\n    }\n\n    @Test\n    public void testIsHttpUrl(){\n        assertTrue(StringUtils.isHttpUrl(\"http://earsadasdsad\"));\n        assertTrue(StringUtils.isHttpUrl(\"https://earsadasdsad\"));\n        assertTrue(StringUtils.isHttpUrl(\"http://\"));\n        assertTrue(StringUtils.isHttpUrl(\"https://\"));\n\n        assertFalse(StringUtils.isHttpUrl(\"dafadfasd\"));\n        assertFalse(StringUtils.isHttpUrl(\"dafadfasd#$@\"));\n        assertFalse(StringUtils.isHttpUrl(\"htt://\"));\n    }\n\n    @Test\n    public void testMergeSlashes(){\n        assertEquals(\"a/b/c/d/e\", StringUtils.mergeSlashesInUrl(\"a////b///c//d/e\"));\n        assertEquals(\"abcd\",StringUtils.mergeSlashesInUrl( \"abcd\"));\n        assertEquals(\"ab/cd\",StringUtils.mergeSlashesInUrl( \"ab/cd\"));\n        assertEquals(\"/abcd\",StringUtils.mergeSlashesInUrl( \"/////abcd\"));\n        assertEquals(\"/abcd/\",StringUtils.mergeSlashesInUrl( \"////abcd///\"));\n        assertEquals(\"/abcd/\",StringUtils.mergeSlashesInUrl( \"/abcd/\"));\n    }\n\n    @Test\n    public void testStartWithVersionString(){\n        assertTrue(StringUtils.startWithVersionString(\"v1\"));\n        assertTrue(StringUtils.startWithVersionString(\"v1fdasfasd\"));\n        assertTrue(StringUtils.startWithVersionString(\"v112fdasfasd\"));\n        assertTrue(StringUtils.startWithVersionString(\"v112/fda/sfasd\"));\n        assertTrue(StringUtils.startWithVersionString(\"v112/fda/v1sfasd\"));\n        assertTrue(StringUtils.startWithVersionString(\"/v112/fda/v1sfasd\"));\n\n        assertFalse(StringUtils.startWithVersionString(\"asdasv1fdasfasd\"));\n        assertFalse(StringUtils.startWithVersionString(\"12v1fdasfasd\"));\n        assertFalse(StringUtils.startWithVersionString(\"asdasv1fdasfasd\"));\n        assertFalse(StringUtils.startWithVersionString(\"wqeasdlv31423423\"));\n        assertFalse(StringUtils.startWithVersionString(\"wqeasdl/v31423423\"));\n        assertFalse(StringUtils.startWithVersionString(\"121fdasfasd\"));\n        assertFalse(StringUtils.startWithVersionString(\"/121fdasfasd\"));\n        assertFalse(StringUtils.startWithVersionString(\"/\"));\n        assertFalse(StringUtils.startWithVersionString(\"/v\"));\n        assertFalse(StringUtils.startWithVersionString(\"\"));\n        assertFalse(StringUtils.startWithVersionString(\"vvv\"));\n        assertFalse(StringUtils.startWithVersionString(\"v\"));\n        assertFalse(StringUtils.startWithVersionString(\"asdvvv\"));\n    }\n\n    @Test\n    public void testRemoveStartingChars(){\n        assertEquals(\"abcde\", StringUtils.removeStartingChars(\"abcde\", 'b'));\n        assertEquals(\"bcde\", StringUtils.removeStartingChars(\"abcde\", 'a'));\n        assertEquals(\"bcde\", StringUtils.removeStartingChars(\"aaaaaabcde\", 'a'));\n        assertEquals(\"bcdeaa\", StringUtils.removeStartingChars(\"aaaaaabcdeaa\", 'a'));\n    }\n}\n"
  },
  {
    "path": "cloudinary-core/src/test/java/com/cloudinary/analytics/AnalyticsTest.java",
    "content": "package com.cloudinary.analytics;\n\nimport com.cloudinary.AuthToken;\nimport com.cloudinary.Cloudinary;\nimport com.cloudinary.utils.Analytics;\nimport org.junit.*;\nimport org.junit.rules.TestName;\n\nimport static org.junit.Assert.assertEquals;\n\npublic class AnalyticsTest {\n\n    public static final String KEY = \"00112233FF99\";\n\n    private Cloudinary cloudinary;\n\n    @Rule\n    public TestName currentTest = new TestName();\n\n    @Before\n    public void setUp() {\n        System.out.println(\"Running \" + this.getClass().getName() + \".\" + currentTest.getMethodName());\n        this.cloudinary = new Cloudinary(\"cloudinary://a:b@test123?load_strategies=false\");\n    }\n\n    @Test\n    public void testEncodeVersion() {\n        Analytics analytics = new Analytics();\n        analytics.setSDKSemver(\"1.24.0\");\n        analytics.setTechVersion(\"12.0.0\");\n        String result = analytics.toQueryParam();\n        Assert.assertEquals(result, \"_a=DAGAlhAMZAA0\");\n\n        analytics.setSDKSemver(\"12.0\");\n        result = analytics.toQueryParam();\n        Assert.assertEquals(result, \"_a=DAGAMAMZAA0\");\n\n        analytics.setSDKSemver(\"43.21.26\");\n        result = analytics.toQueryParam();\n        Assert.assertEquals(result, \"_a=DAG///AMZAA0\");\n\n        analytics.setSDKSemver(\"0.0.0\");\n        result = analytics.toQueryParam();\n        Assert.assertEquals(result, \"_a=DAGAAAAMZAA0\");\n\n        analytics.setSDKSemver(\"43.21.27\");\n        result = analytics.toQueryParam();\n        Assert.assertEquals(result, \"_a=E\");\n\n    }\n\n    @Test\n    public void testToQueryParam() {\n        Analytics analytics = new Analytics(\"F\", \"2.0.0\", \"1.8.0\", \"Z\", \"1.34.0\", \"0\");\n        String result = analytics.toQueryParam();\n        Assert.assertEquals(result, \"_a=DAFAACMhZBi0\");\n\n        analytics = new Analytics(\"F\", \"2.0.0\", \"1.8.0\", \"Z\", \"16.3\", \"0\");\n        result = analytics.toQueryParam();\n        Assert.assertEquals(result, \"_a=DAFAACMhZQD0\");\n    }\n\n    @Test\n    public void testUrlWithAnalytics() {\n        cloudinary.config.analytics = true;\n        cloudinary.setAnalytics(new Analytics(\"F\", \"2.0.0\", \"1.8.0\", \"Z\", \"1.34.0\", \"0\"));\n        String url = cloudinary.url().generate(\"test\");\n        Assert.assertEquals(url, \"https://res.cloudinary.com/test123/image/upload/test?_a=DAFAACMhZBi0\");\n    }\n\n    @Test\n    public void testUrlWithNoAnalytics() {\n        cloudinary.config.analytics = false;\n        String url = cloudinary.url().secure(true).generate(\"test\");\n        Assert.assertEquals(url, \"https://res.cloudinary.com/test123/image/upload/test\");\n    }\n\n    @Test\n    public void testUrlWithNoAnalyticsDefined() {\n        cloudinary.config.analytics = false;\n        String url = cloudinary.url().generate(\"test\");\n        Assert.assertEquals(url, \"https://res.cloudinary.com/test123/image/upload/test\");\n    }\n\n    @Test\n    public void testUrlWithNoAnalyticsNull() {\n        cloudinary.config.analytics = false;\n        String url = cloudinary.url().generate(\"test\");\n        Assert.assertEquals(url, \"https://res.cloudinary.com/test123/image/upload/test\");\n    }\n\n    @Test\n    public void testUrlWithNoAnalyticsNullAndTrue() {\n        cloudinary.config.analytics = true;\n        cloudinary.analytics.setSDKSemver(\"1.30.0\");\n        cloudinary.analytics.setTechVersion(\"12.0.0\");\n        String url = cloudinary.url().generate(\"test\");\n        Assert.assertEquals(url, \"https://res.cloudinary.com/test123/image/upload/test?_a=DAGAu5AMZAA0\");\n    }\n\n    @Test\n    public void testMiscAnalyticsObject() {\n        cloudinary.config.analytics = true;\n        Analytics analytics = new Analytics(\"Z\", \"1.24.0\", \"12.0.0\", \"Z\", \"1.34.0\", \"0\");\n        String result = analytics.toQueryParam();\n        Assert.assertEquals(result, \"_a=DAZAlhAMZBi0\");\n    }\n\n    @Test\n    public void testErrorAnalytics() {\n        cloudinary.config.analytics = true;\n        Analytics analytics = new Analytics(\"Z\", \"1.24.0\", \"0\", \"Z\", \"1.34.0\", \"0\");\n        String result = analytics.toQueryParam();\n        Assert.assertEquals(result, \"_a=E\");\n    }\n\n    @Test\n    public void testUrlNoAnalyticsWithQueryParams() {\n        final AuthToken authToken = new AuthToken(KEY).duration(300);\n        authToken.startTime(11111111); // start time is set for test purposes\n        cloudinary.config.authToken = authToken;\n        cloudinary.config.cloudName = \"test123\";\n\n        cloudinary.config.analytics = true;\n        cloudinary.setAnalytics(new Analytics(\"F\", \"2.0.0\", System.getProperty(\"java.version\"), \"Z\", System.getProperty(\"os.version\"), \"0\"));\n        cloudinary.config.privateCdn = true;\n        String url = cloudinary.url().signed(true).type(\"authenticated\").generate(\"test\");\n        assertEquals(url,\"https://test123-res.cloudinary.com/image/authenticated/test?__cld_token__=st=11111111~exp=11111411~hmac=735a49389a72ac0b90d1a84ac5d43facd1a9047f153b39e914747ef6ed195e53\");\n        cloudinary.config.privateCdn = false;\n    }\n\n    @Test\n    public void testFeatureFlag() {\n        Analytics analytics = new Analytics(\"F\", \"2.0.0\", \"1.8.0\", \"Z\", \"1.34.0\", \"0\");\n        analytics.setFeatureFlag(\"F\");\n        String result = analytics.toQueryParam();\n        Assert.assertEquals(result, \"_a=DAFAACMhZBiF\");\n    }\n\n    @After\n    public void tearDown() {\n        cloudinary.config.analytics = false;\n        cloudinary.analytics = null;\n    }\n\n}\n"
  },
  {
    "path": "cloudinary-core/src/test/java/com/cloudinary/api/signing/ApiResponseSignatureVerifierTest.java",
    "content": "package com.cloudinary.api.signing;\n\nimport com.cloudinary.SignatureAlgorithm;\nimport org.junit.Test;\n\nimport static org.junit.Assert.assertFalse;\nimport static org.junit.Assert.assertTrue;\n\npublic class ApiResponseSignatureVerifierTest {\n    @Test\n    public void testVerifySignature() {\n        ApiResponseSignatureVerifier verifier = new ApiResponseSignatureVerifier(\"X7qLTrsES31MzxxkxPPA-pAGGfU\");\n\n        boolean actual = verifier.verifySignature(\"tests/logo.png\", \"1\", \"08d3107a5b2ad82e7d82c0b972218fbf20b5b1e0\");\n\n        assertTrue(actual);\n    }\n\n    @Test\n    public void testVerifySignatureFail() {\n        ApiResponseSignatureVerifier verifier = new ApiResponseSignatureVerifier(\"X7qLTrsES31MzxxkxPPA-pAGGfU\");\n\n        boolean actual = verifier.verifySignature(\"tests/logo.png\", \"1\", \"doesNotMatchForSure\");\n\n        assertFalse(actual);\n    }\n\n    @Test\n    public void testVerifySignatureSHA256() {\n        ApiResponseSignatureVerifier verifier = new ApiResponseSignatureVerifier(\"X7qLTrsES31MzxxkxPPA-pAGGfU\", SignatureAlgorithm.SHA256);\n\n        boolean actual = verifier.verifySignature(\"tests/logo.png\", \"1\", \"cc69ae4ed73303fbf4a55f2ae5fc7e34ad3a5c387724bfcde447a2957cacdfea\");\n\n        assertTrue(actual);\n    }\n}\n"
  },
  {
    "path": "cloudinary-core/src/test/java/com/cloudinary/api/signing/NotificationRequestSignatureVerifierTest.java",
    "content": "package com.cloudinary.api.signing;\n\nimport com.cloudinary.SignatureAlgorithm;\nimport org.junit.Test;\n\nimport static org.junit.Assert.assertFalse;\nimport static org.junit.Assert.assertTrue;\n\npublic class NotificationRequestSignatureVerifierTest {\n    @Test\n    public void testVerifySignature() {\n        NotificationRequestSignatureVerifier verifier = new NotificationRequestSignatureVerifier(\"someApiSecret\");\n\n        boolean actual = verifier.verifySignature(\n                \"{}\",\n                \"0\",\n                \"f9aa4471d2a88ff244424cca2444edf7d7ac3596\");\n\n        assertTrue(actual);\n    }\n\n    @Test\n    public void testVerifySignatureFailWhenSignatureDoesntMatch() {\n        NotificationRequestSignatureVerifier verifier = new NotificationRequestSignatureVerifier(\"someApiSecret\");\n\n        boolean actual = verifier.verifySignature(\n                \"{}\",\n                \"0\",\n                \"notMatchingForSure\");\n\n        assertFalse(actual);\n    }\n\n    @Test\n    public void testVerifySignatureFailWhenTooOld() {\n        NotificationRequestSignatureVerifier verifier = new NotificationRequestSignatureVerifier(\"someApiSecret\");\n\n        boolean actual = verifier.verifySignature(\n                \"{}\",\n                \"0\",\n                \"f9aa4471d2a88ff244424cca2444edf7d7ac3596\",\n                1000L);\n\n        assertFalse(actual);\n    }\n\n    @Test\n    public void testVerifySignaturePassWhenStillValid() {\n        NotificationRequestSignatureVerifier verifier = new NotificationRequestSignatureVerifier(\"someApiSecret\");\n\n        boolean actual = verifier.verifySignature(\n                \"{}\",\n                \"0\",\n                \"f9aa4471d2a88ff244424cca2444edf7d7ac3596\",\n                Long.MAX_VALUE / 1000L);\n\n        assertTrue(actual);\n    }\n\n    @Test\n    public void testVerifySignatureFailWhenStillValidButSignatureDoesntMatch() {\n        NotificationRequestSignatureVerifier verifier = new NotificationRequestSignatureVerifier(\"someApiSecret\");\n\n        boolean actual = verifier.verifySignature(\n                \"{}\",\n                \"0\",\n                \"notMatchingForSure\",\n                Long.MAX_VALUE / 1000L);\n\n        assertFalse(actual);\n    }\n\n    @Test\n    public void testVerifySignatureSHA256() {\n        NotificationRequestSignatureVerifier verifier = new NotificationRequestSignatureVerifier(\"someApiSecret\", SignatureAlgorithm.SHA256);\n\n        boolean actual = verifier.verifySignature(\n                \"{}\",\n                \"0\",\n                \"d5497e1a206ad0ba29ad09a7c0c5f22e939682d15009c15ab3199f62fefbd14b\");\n\n        assertTrue(actual);\n    }\n}\n"
  },
  {
    "path": "cloudinary-core/src/test/java/com/cloudinary/test/CloudinaryTest.java",
    "content": "package com.cloudinary.test;\n\nimport com.cloudinary.*;\nimport com.cloudinary.transformation.*;\nimport com.cloudinary.utils.ObjectUtils;\nimport junitparams.JUnitParamsRunner;\nimport junitparams.Parameters;\nimport junitparams.naming.TestCaseName;\nimport org.cloudinary.json.JSONObject;\nimport org.junit.Before;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.rules.TestName;\nimport org.junit.runner.RunWith;\n\nimport java.io.IOException;\nimport java.io.UnsupportedEncodingException;\nimport java.lang.reflect.Field;\nimport java.lang.reflect.Modifier;\nimport java.lang.reflect.Type;\nimport java.net.URI;\nimport java.net.URISyntaxException;\nimport java.net.URLDecoder;\nimport java.net.URLEncoder;\nimport java.util.*;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\nimport static com.cloudinary.CustomFunction.remote;\nimport static com.cloudinary.CustomFunction.wasm;\nimport static com.cloudinary.utils.ObjectUtils.asMap;\nimport static com.cloudinary.utils.ObjectUtils.emptyMap;\nimport static org.junit.Assert.*;\n\n@RunWith(JUnitParamsRunner.class)\npublic class CloudinaryTest {\n    private static final String DEFAULT_ROOT_PATH = \"https://res.cloudinary.com/test123/\";\n    private static final String DEFAULT_UPLOAD_PATH = DEFAULT_ROOT_PATH + \"image/upload/\";\n    private static final String VIDEO_UPLOAD_PATH = DEFAULT_ROOT_PATH + \"video/upload/\";\n    private Cloudinary cloudinary;\n\n    @Rule\n    public TestName currentTest = new TestName();\n\n    @Before\n    public void setUp() {\n        System.out.println(\"Running \" + this.getClass().getName() + \".\" + currentTest.getMethodName());\n        this.cloudinary = new Cloudinary(\"cloudinary://a:b@test123?load_strategies=false&analytics=false\");\n    }\n\n    @Test\n    public void testUrlSuffixWithDotOrSlash() {\n        Boolean[] errors = new Boolean[4];\n        try {\n            cloudinary.url().suffix(\"dsfdfd.adsfad\").generate(\"publicId\");\n        } catch (IllegalArgumentException e) {\n            errors[0] = true;\n        }\n\n        try {\n            cloudinary.url().suffix(\"dsfdfd/adsfad\").generate(\"publicId\");\n        } catch (IllegalArgumentException e) {\n            errors[1] = true;\n        }\n\n        try {\n            cloudinary.url().suffix(\"dsfd.fd/adsfad\").generate(\"publicId\");\n        } catch (IllegalArgumentException e) {\n            errors[2] = true;\n        }\n\n        try {\n            cloudinary.url().suffix(\"dsfdfdaddsfad\").generate(\"publicId\");\n        } catch (IllegalArgumentException e) {\n            errors[3] = true;\n        }\n\n        assertTrue(errors[0]);\n        assertTrue(errors[1]);\n        assertTrue(errors[2]);\n        assertNull(errors[3]);\n    }\n\n    @Test\n    public void testCloudName() {\n        // should use cloud_name from config\n        String result = cloudinary.url().generate(\"test\");\n        assertEquals(DEFAULT_UPLOAD_PATH + \"test\", result);\n    }\n\n    @Test\n    public void testCloudNameOptions() {\n        // should allow overriding cloud_name in options\n        String result = cloudinary.url().cloudName(\"test321\").generate(\"test\");\n        assertEquals(\"https://res.cloudinary.com/test321/image/upload/test\", result);\n    }\n\n    @Test\n    public void testSecureDistribution() {\n        // should use default secure distribution if secure=TRUE\n        String result = cloudinary.url().generate(\"test\");\n        assertEquals(\"https://res.cloudinary.com/test123/image/upload/test\", result);\n    }\n\n    @Test\n    public void testTextLayerStyleIdentifierVariables() {\n        String url = cloudinary.url().transformation(\n                new Transformation()\n                        .variable(\"$style\", \"!Arial_12!\")\n                        .chain()\n                        .overlay(\n                                new TextLayer().text(\"hello-world\").textStyle(\"$style\")\n                        )).generate(\"sample\");\n\n        assertEquals(\"https://res.cloudinary.com/test123/image/upload/$style_!Arial_12!/l_text:$style:hello-world/sample\", url);\n\n        url = cloudinary.url().transformation(\n                new Transformation()\n                        .variable(\"$style\", \"!Arial_12!\")\n                        .chain()\n                        .overlay(\n                                new TextLayer().text(\"hello-world\").textStyle(new Expression(\"$style\"))\n                        )).generate(\"sample\");\n\n        assertEquals(\"https://res.cloudinary.com/test123/image/upload/$style_!Arial_12!/l_text:$style:hello-world/sample\", url);\n    }\n\n\n    @Test\n    public void testSecureDistributionOverwrite() {\n        // should allow overwriting secure distribution if secure=TRUE\n        String result = cloudinary.url().secureDistribution(\"something.else.com\").generate(\"test\");\n        assertEquals(\"https://something.else.com/test123/image/upload/test\", result);\n    }\n\n    @Test\n    public void testSecureDistibution() {\n        // should take secure distribution from config if secure=TRUE\n        cloudinary.config.secureDistribution = \"config.secure.distribution.com\";\n        String result = cloudinary.url().secure(true).generate(\"test\");\n        assertEquals(\"https://config.secure.distribution.com/test123/image/upload/test\", result);\n    }\n\n    @Test\n    public void testSecureAkamai() {\n        // should default to akamai if secure is given with private_cdn and no\n        // secure_distribution\n        cloudinary.config.secure = true;\n        cloudinary.config.privateCdn = true;\n        String result = cloudinary.url().generate(\"test\");\n        assertEquals(\"https://test123-res.cloudinary.com/image/upload/test\", result);\n    }\n\n    @Test\n    public void testSecureNonAkamai() {\n        // should not add cloud_name if private_cdn and secure non akamai\n        // secure_distribution\n        cloudinary.config.secure = true;\n        cloudinary.config.privateCdn = true;\n        cloudinary.config.secureDistribution = \"something.cloudfront.net\";\n        String result = cloudinary.url().generate(\"test\");\n        assertEquals(\"https://something.cloudfront.net/image/upload/test\", result);\n    }\n\n    @Test\n    public void testHttpPrivateCdn() {\n        // should not add cloud_name if private_cdn and not secure\n        cloudinary.config.privateCdn = true;\n        String result = cloudinary.url().generate(\"test\");\n        assertEquals(\"https://test123-res.cloudinary.com/image/upload/test\", result);\n    }\n\n    @Test\n    public void testFormat() {\n        // should use format from options\n        String result = cloudinary.url().format(\"jpg\").generate(\"test\");\n        assertEquals(DEFAULT_UPLOAD_PATH + \"test.jpg\", result);\n    }\n\n    @Test\n    public void testType() {\n        // should use type from options\n        String result = cloudinary.url().type(\"facebook\").generate(\"test\");\n        assertEquals(\"https://res.cloudinary.com/test123/image/facebook/test\", result);\n    }\n\n    @Test\n    public void testResourceType() {\n        // should use resource_type from options\n        String result = cloudinary.url().resourcType(\"raw\").generate(\"test\");\n        assertEquals(\"https://res.cloudinary.com/test123/raw/upload/test\", result);\n    }\n\n    @Test\n    public void testIgnoreHttp() {\n        // should ignore http links only if type is not given or is asset\n        String result = cloudinary.url().generate(\"http://test\");\n        assertEquals(\"http://test\", result);\n        result = cloudinary.url().type(\"asset\").generate(\"http://test\");\n        assertEquals(\"http://test\", result);\n        result = cloudinary.url().type(\"fetch\").generate(\"http://test\");\n        assertEquals(\"https://res.cloudinary.com/test123/image/fetch/http://test\", result);\n    }\n\n    @Test\n    public void testFetch() {\n        // should escape fetch urls\n        String result = cloudinary.url().type(\"fetch\").generate(\"http://blah.com/hello?a=b\");\n        assertEquals(\"https://res.cloudinary.com/test123/image/fetch/http://blah.com/hello%3Fa%3Db\", result);\n    }\n\n    @Test\n    public void testCname() {\n        // should support external cname\n        String result = cloudinary.url().cname(\"hello.com\").secure(false).generate(\"test\");\n        assertEquals(\"http://hello.com/test123/image/upload/test\", result);\n    }\n\n    @Test\n    public void testCnameSubdomain() {\n        // should support external cname with cdn_subdomain on\n        String result = cloudinary.url().cname(\"hello.com\").cdnSubdomain(true).secure(false).generate(\"test\");\n        assertEquals(\"http://a2.hello.com/test123/image/upload/test\", result);\n    }\n\n    @Test(expected = IllegalArgumentException.class)\n    public void testDisallowUrlSuffixInNonUploadTypes() {\n        cloudinary.url().suffix(\"hello\").privateCdn(true).type(\"facebook\").generate(\"test\");\n\n    }\n\n    @Test(expected = IllegalArgumentException.class)\n    public void testDisallowUrlSuffixWithSlash() {\n        cloudinary.url().suffix(\"hello/world\").privateCdn(true).generate(\"test\");\n    }\n\n    @Test(expected = IllegalArgumentException.class)\n    public void testDisallowUrlSuffixWithDot() {\n        cloudinary.url().suffix(\"hello.world\").privateCdn(true).generate(\"test\");\n    }\n\n    @Test\n    public void testSupportUrlSuffixForPrivateCdn() {\n        String actual = cloudinary.url().suffix(\"hello\").privateCdn(true).generate(\"test\");\n        assertEquals(\"https://test123-res.cloudinary.com/images/test/hello\", actual);\n\n        actual = cloudinary.url().suffix(\"hello\").privateCdn(true).transformation(new Transformation().angle(0)).generate(\"test\");\n        assertEquals(\"https://test123-res.cloudinary.com/images/a_0/test/hello\", actual);\n\n    }\n\n    @Test\n    public void testPutFormatAfterUrlSuffix() {\n        String actual = cloudinary.url().suffix(\"hello\").privateCdn(true).format(\"jpg\").generate(\"test\");\n        assertEquals(\"https://test123-res.cloudinary.com/images/test/hello.jpg\", actual);\n    }\n\n    @Test\n    public void testNotSignTheUrlSuffix() {\n\n        Pattern pattern = Pattern.compile(\"s--[0-9A-Za-z_-]{8}--\");\n        String url = cloudinary.url().format(\"jpg\").signed(true).generate(\"test\");\n        Matcher matcher = pattern.matcher(url);\n        matcher.find();\n        String expectedSignature = url.substring(matcher.start(), matcher.end());\n\n        String actual = cloudinary.url().format(\"jpg\").privateCdn(true).signed(true).suffix(\"hello\").generate(\"test\");\n        assertEquals(\"https://test123-res.cloudinary.com/images/\" + expectedSignature + \"/test/hello.jpg\", actual);\n\n        url = cloudinary.url().format(\"jpg\").signed(true).transformation(new Transformation().angle(0)).generate(\"test\");\n        matcher = pattern.matcher(url);\n        matcher.find();\n        expectedSignature = url.substring(matcher.start(), matcher.end());\n\n        actual = cloudinary.url().format(\"jpg\").privateCdn(true).signed(true).suffix(\"hello\").transformation(new Transformation().angle(0)).generate(\"test\");\n\n        assertEquals(\"https://test123-res.cloudinary.com/images/\" + expectedSignature + \"/a_0/test/hello.jpg\", actual);\n    }\n\n    @Test\n    public void testSignatureLength(){\n        String url = cloudinary.url().signed(true).generate(\"sample.jpg\");\n        assertEquals(\"https://res.cloudinary.com/test123/image/upload/s--v2fTPYTu--/sample.jpg\", url);\n\n        url = cloudinary.url().signed(true).longUrlSignature(true).generate(\"sample.jpg\");\n        assertEquals(\"https://res.cloudinary.com/test123/image/upload/s--2hbrSMPOjj5BJ4xV7SgFbRDevFaQNUFf--/sample.jpg\", url);\n    }\n\n    @Test\n    public void testSupportUrlSuffixForRawUploads() {\n        String actual = cloudinary.url().suffix(\"hello\").privateCdn(true).resourceType(\"raw\").generate(\"test\");\n        assertEquals(\"https://test123-res.cloudinary.com/files/test/hello\", actual);\n    }\n\n    @Test\n    public void testSupportUrlSuffixForVideoUploads() {\n        String actual = cloudinary.url().suffix(\"hello\").privateCdn(true).resourceType(\"video\").generate(\"test\");\n        assertEquals(\"https://test123-res.cloudinary.com/videos/test/hello\", actual);\n    }\n\n    @Test\n    public void testSupportUrlSuffixForAuthenticatedImages() {\n        String actual = cloudinary.url().suffix(\"hello\").privateCdn(true).resourceType(\"image\").type(\"authenticated\").generate(\"test\");\n        assertEquals(\"https://test123-res.cloudinary.com/authenticated_images/test/hello\", actual);\n    }\n\n    @Test\n    public void testSupportUrlSuffixForPrivateImages() {\n        String actual = cloudinary.url().suffix(\"hello\").privateCdn(true).resourceType(\"image\").type(\"private\").generate(\"test\");\n        assertEquals(\"https://test123-res.cloudinary.com/private_images/test/hello\", actual);\n    }\n\n    @Test\n    public void testSupportUseRootPathForPrivateCdn() {\n        String actual = cloudinary.url().privateCdn(true).useRootPath(true).generate(\"test\");\n        assertEquals(\"https://test123-res.cloudinary.com/test\", actual);\n\n        actual = cloudinary.url().privateCdn(true).transformation(new Transformation().angle(0)).useRootPath(true).generate(\"test\");\n        assertEquals(\"https://test123-res.cloudinary.com/a_0/test\", actual);\n    }\n\n    @Test\n    public void testSupportUseRootPathTogetherWithUrlSuffixForPrivateCdn() {\n\n        String actual = cloudinary.url().privateCdn(true).suffix(\"hello\").useRootPath(true).generate(\"test\");\n        assertEquals(\"https://test123-res.cloudinary.com/test/hello\", actual);\n\n    }\n\n    @Test(expected = IllegalArgumentException.class)\n    public void testDisllowUseRootPathIfNotImageUploadForFacebook() {\n        cloudinary.url().useRootPath(true).privateCdn(true).type(\"facebook\").generate(\"test\");\n    }\n\n    @Test(expected = IllegalArgumentException.class)\n    public void testDisllowUseRootPathIfNotImageUploadForRaw() {\n        cloudinary.url().useRootPath(true).privateCdn(true).resourceType(\"raw\").generate(\"test\");\n    }\n\n    @Test\n    public void testCrop() {\n        Transformation transformation = new Transformation().width(100).height(101);\n        String result = cloudinary.url().transformation(transformation).generate(\"test\");\n        assertEquals(DEFAULT_UPLOAD_PATH + \"h_101,w_100/test\", result);\n        assertEquals(\"101\", transformation.getHtmlHeight());\n        assertEquals(\"100\", transformation.getHtmlWidth());\n        transformation = new Transformation().width(100).height(101).crop(\"crop\");\n        result = cloudinary.url().transformation(transformation).generate(\"test\");\n        assertEquals(DEFAULT_UPLOAD_PATH + \"c_crop,h_101,w_100/test\", result);\n    }\n\n    @Test\n    public void testVariousOptions() {\n        // should use x, y, radius, prefix, gravity and quality from options\n        Transformation transformation = new Transformation().x(1).y(2).radius(3).gravity(\"center\").quality(0.4).prefix(\"a\");\n        String result = cloudinary.url().transformation(transformation).generate(\"test\");\n        assertEquals(DEFAULT_UPLOAD_PATH + \"g_center,p_a,q_0.4,r_3,x_1,y_2/test\", result);\n    }\n\n    @Test\n    @TestCaseName(\"{method}: {params}\")\n    @Parameters\n    public void testQuality(Object quality, String result) {\n        Transformation transformation = new Transformation().quality(quality);\n        assertEquals(result, transformation.generate());\n    }\n\n    @SuppressWarnings(\"unused\")\n    private Object[][] parametersForTestQuality() {\n        return new Object[][]{\n                {0.4, \"q_0.4\"},\n                {\"0.4\", \"q_0.4\"},\n                {\"auto\", \"q_auto\"},\n                {\"auto:good\", \"q_auto:good\"}};\n\n    }\n\n    @Test\n    @TestCaseName(\"{method}: {0}\")\n    @Parameters\n    public void testAutoGravity(String value, String serialized) {\n        Transformation transformation = new Transformation().crop(\"crop\").gravity(value).width(0.5f);\n        String result = cloudinary.url().transformation(transformation).generate(\"test\");\n        assertEquals(DEFAULT_UPLOAD_PATH + \"c_crop,\" + serialized + \",w_0.5/test\", result);\n    }\n\n    @SuppressWarnings(\"unused\")\n    private String[][] parametersForTestAutoGravity() {\n        return new String[][]{\n                {\"west\", \"g_west\"},\n                {\"auto\", \"g_auto\"},\n                {\"auto:good\", \"g_auto:good\"},\n                {\"auto:ocr_text\", \"g_auto:ocr_text\"},\n                {\"ocr_text\", \"g_ocr_text\"},\n                {\"ocr_text:adv_ocr\", \"g_ocr_text:adv_ocr\"}\n        };\n\n    }\n\n    @Test\n    public void testTransformationSimple() {\n        // should support named transformation\n        Transformation transformation = new Transformation().named(\"blip\");\n        String result = cloudinary.url().transformation(transformation).generate(\"test\");\n        assertEquals(DEFAULT_UPLOAD_PATH + \"t_blip/test\", result);\n    }\n\n    @Test\n    public void testTransformationArray() {\n        // should support array of named transformations\n        Transformation transformation = new Transformation().named(\"blip\", \"blop\");\n        String result = cloudinary.url().transformation(transformation).generate(\"test\");\n        assertEquals(DEFAULT_UPLOAD_PATH + \"t_blip.blop/test\", result);\n    }\n\n    @Test\n    public void testNamedTransformationWithSpaces() {\n        // should support named transformations with spaces\n        Transformation transformation = new Transformation().named(\"blip blop\");\n        String result = cloudinary.url().transformation(transformation).generate(\"test\");\n        assertEquals(DEFAULT_UPLOAD_PATH + \"t_blip%20blop/test\", result);\n    }\n\n    @Test\n    public void testBaseTransformations() {\n        // should support base transformation\n        Transformation transformation = new Transformation().x(100).y(100).crop(\"fill\").chain().crop(\"crop\").width(100);\n        String result = cloudinary.url().transformation(transformation).generate(\"test\");\n        assertEquals(\"100\", transformation.getHtmlWidth());\n        assertEquals(DEFAULT_UPLOAD_PATH + \"c_fill,x_100,y_100/c_crop,w_100/test\", result);\n    }\n\n    @Test\n    public void testBaseTransformationArray() {\n        // should support array of base transformations\n        Transformation transformation = new Transformation().x(100).y(100).width(200).crop(\"fill\").chain().radius(10).chain().crop(\"crop\").width(100);\n        String result = cloudinary.url().transformation(transformation).generate(\"test\");\n        assertEquals(\"100\", transformation.getHtmlWidth());\n        assertEquals(DEFAULT_UPLOAD_PATH + \"c_fill,w_200,x_100,y_100/r_10/c_crop,w_100/test\", result);\n    }\n\n    @Test\n    public void testNoEmptyTransformation() {\n        // should not include empty transformations\n        Transformation transformation = new Transformation().chain().x(100).y(100).crop(\"fill\").chain();\n        String result = cloudinary.url().transformation(transformation).generate(\"test\");\n        assertEquals(DEFAULT_UPLOAD_PATH + \"c_fill,x_100,y_100/test\", result);\n    }\n\n    @Test\n    public void testHttpEscape() {\n        // should escape http urls\n        String result = cloudinary.url().type(\"youtube\").generate(\"http://www.youtube.com/watch?v=d9NF2edxy-M\");\n        assertEquals(\"https://res.cloudinary.com/test123/image/youtube/http://www.youtube.com/watch%3Fv%3Dd9NF2edxy-M\", result);\n    }\n\n    @Test\n    public void testBackground() {\n        // should support background\n        Transformation transformation = new Transformation().background(\"red\");\n        String result = cloudinary.url().transformation(transformation).generate(\"test\");\n        assertEquals(DEFAULT_UPLOAD_PATH + \"b_red/test\", result);\n        transformation = new Transformation().background(\"#112233\");\n        result = cloudinary.url().transformation(transformation).generate(\"test\");\n        assertEquals(DEFAULT_UPLOAD_PATH + \"b_rgb:112233/test\", result);\n    }\n\n    @Test\n    public void testDefaultImage() {\n        // should support default_image\n        Transformation transformation = new Transformation().defaultImage(\"default\");\n        String result = cloudinary.url().transformation(transformation).generate(\"test\");\n        assertEquals(DEFAULT_UPLOAD_PATH + \"d_default/test\", result);\n    }\n\n    @Test\n    public void testAngle() {\n        // should support angle\n        Transformation transformation = new Transformation().angle(12);\n        String result = cloudinary.url().transformation(transformation).generate(\"test\");\n        assertEquals(DEFAULT_UPLOAD_PATH + \"a_12/test\", result);\n        transformation = new Transformation().angle(\"exif\", \"12\");\n        result = cloudinary.url().transformation(transformation).generate(\"test\");\n        assertEquals(DEFAULT_UPLOAD_PATH + \"a_exif.12/test\", result);\n    }\n\n    @Test\n    public void testFetchFormat() {\n        // should support format for fetch urls\n        String result = cloudinary.url().format(\"jpg\").type(\"fetch\").generate(\"http://cloudinary.com/images/old_logo.png\");\n        assertEquals(\"https://res.cloudinary.com/test123/image/fetch/f_jpg/http://cloudinary.com/images/old_logo.png\", result);\n    }\n\n    @Test\n    public void testUseFetchFormat() {\n        // should support use fetch format, adds the format but not an extension\n        String result = cloudinary.url().format(\"jpg\").useFetchFormat(true).generate(\"old_logo\");\n        assertEquals(\"https://res.cloudinary.com/test123/image/upload/f_jpg/old_logo\", result);\n    }\n\n    @Test\n    public void testEffect() {\n        // should support effect\n        Transformation transformation = new Transformation().effect(\"sepia\");\n        String result = cloudinary.url().transformation(transformation).generate(\"test\");\n        assertEquals(DEFAULT_UPLOAD_PATH + \"e_sepia/test\", result);\n    }\n\n    @Test\n    public void testEffectWithParam() {\n        // should support effect with param\n        Transformation transformation = new Transformation().effect(\"sepia\", 10);\n        String result = cloudinary.url().transformation(transformation).generate(\"test\");\n        assertEquals(DEFAULT_UPLOAD_PATH + \"e_sepia:10/test\", result);\n    }\n\n    @Test\n    public void testArtisticFilter() {\n        Transformation transformation = new Transformation().effect(\"art\", \"incognito\");\n        String result = cloudinary.url().transformation(transformation).generate(\"test\");\n        assertEquals(DEFAULT_UPLOAD_PATH + \"e_art:incognito/test\", result);\n    }\n\n    @Test\n    public void testDensity() {\n        // should support density\n        Transformation transformation = new Transformation().density(150);\n        String result = cloudinary.url().transformation(transformation).generate(\"test\");\n        assertEquals(DEFAULT_UPLOAD_PATH + \"dn_150/test\", result);\n    }\n\n    @Test\n    public void testPage() {\n        // should support page\n        Transformation transformation = new Transformation().page(5);\n        String result = cloudinary.url().transformation(transformation).generate(\"test\");\n        assertEquals(DEFAULT_UPLOAD_PATH + \"pg_5/test\", result);\n    }\n\n    @Test\n    public void testBorder() {\n        // should support border\n        Transformation transformation = new Transformation().border(5, \"black\");\n        String result = cloudinary.url().transformation(transformation).generate(\"test\");\n        assertEquals(DEFAULT_UPLOAD_PATH + \"bo_5px_solid_black/test\", result);\n        transformation = new Transformation().border(5, \"#ffaabbdd\");\n        result = cloudinary.url().transformation(transformation).generate(\"test\");\n        assertEquals(DEFAULT_UPLOAD_PATH + \"bo_5px_solid_rgb:ffaabbdd/test\", result);\n        transformation = new Transformation().border(\"1px_solid_blue\");\n        result = cloudinary.url().transformation(transformation).generate(\"test\");\n        assertEquals(DEFAULT_UPLOAD_PATH + \"bo_1px_solid_blue/test\", result);\n    }\n\n    @Test\n    public void testFlags() {\n        // should support flags\n        Transformation transformation = new Transformation().flags(\"abc\");\n        String result = cloudinary.url().transformation(transformation).generate(\"test\");\n        assertEquals(DEFAULT_UPLOAD_PATH + \"fl_abc/test\", result);\n        transformation = new Transformation().flags(\"abc\", \"def\");\n        result = cloudinary.url().transformation(transformation).generate(\"test\");\n        assertEquals(DEFAULT_UPLOAD_PATH + \"fl_abc.def/test\", result);\n    }\n\n    @Test\n    public void testOpacity() {\n        // should support opacity\n        Transformation transformation = new Transformation().opacity(50);\n        String result = cloudinary.url().transformation(transformation).generate(\"test\");\n        assertEquals(DEFAULT_UPLOAD_PATH + \"o_50/test\", result);\n\n        transformation = new Transformation().opacity(\"$var\");\n        result = cloudinary.url().transformation(transformation).generate(\"test\");\n        assertEquals(DEFAULT_UPLOAD_PATH + \"o_$var/test\", result);\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    @Test\n    public void testImageTag() {\n        Transformation transformation = new Transformation().width(100).height(101).crop(\"crop\");\n        String result = cloudinary.url().transformation(transformation).imageTag(\"test\", asMap(\"alt\", \"my image\"));\n        assertEquals(\"<img src='https://res.cloudinary.com/test123/image/upload/c_crop,h_101,w_100/test' alt='my image' height='101' width='100'/>\", result);\n        transformation = new Transformation().width(0.9).height(0.9).crop(\"crop\").responsiveWidth(true);\n        result = cloudinary.url().transformation(transformation).imageTag(\"test\", asMap(\"alt\", \"my image\"));\n        assertEquals(\n                \"<img alt='my image' class='cld-responsive' data-src='https://res.cloudinary.com/test123/image/upload/c_crop,h_0.9,w_0.9/c_limit,w_auto/test'/>\",\n                result);\n        result = cloudinary.url().transformation(transformation).imageTag(\"test\", asMap(\"alt\", \"my image\", \"class\", \"extra\"));\n        assertEquals(\n                \"<img alt='my image' class='extra cld-responsive' data-src='https://res.cloudinary.com/test123/image/upload/c_crop,h_0.9,w_0.9/c_limit,w_auto/test'/>\",\n                result);\n        transformation = new Transformation().width(\"auto\").crop(\"crop\");\n        result = cloudinary.url().transformation(transformation).imageTag(\"test\", asMap(\"alt\", \"my image\", \"responsive_placeholder\", \"blank\"));\n        assertEquals(\n                \"<img src='data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7' alt='my image' class='cld-responsive' data-src='https://res.cloudinary.com/test123/image/upload/c_crop,w_auto/test'/>\",\n                result);\n        result = cloudinary.url().transformation(transformation).imageTag(\"test\", asMap(\"alt\", \"my image\", \"responsive_placeholder\", \"other.gif\"));\n        assertEquals(\n                \"<img src='other.gif' alt='my image' class='cld-responsive' data-src='https://res.cloudinary.com/test123/image/upload/c_crop,w_auto/test'/>\",\n                result);\n    }\n\n    @Test\n    public void testClientHints() {\n        String testTag;\n        String message = \"should not implement responsive behaviour if client hints is true\";\n        cloudinary.config.clientHints = true;\n        Transformation trans = new Transformation()\n                .crop(\"scale\")\n                .width(\"auto\")\n                .dpr(\"auto\");\n        testTag = cloudinary.url().transformation(trans).imageTag(\"sample.jpg\");\n        assertTrue(testTag.startsWith(\"<img\"));\n        assertFalse(message, testTag.contains(\"class=\"));\n        assertFalse(message, testTag.contains(\"data-src\"));\n        assertTrue(message, testTag.contains(\"src='https://res.cloudinary.com/test123/image/upload/c_scale,dpr_auto,w_auto/sample.jpg'\"));\n        testTag = cloudinary.url().transformation(trans).imageTag(\"sample.jpg\");\n        assertTrue(testTag.startsWith(\"<img\"));\n        assertFalse(testTag.contains(\"class=\"));\n        assertFalse(message, testTag.contains(\"data-src\"));\n        assertTrue(message, testTag.contains(\"src='https://res.cloudinary.com/test123/image/upload/c_scale,dpr_auto,w_auto/sample.jpg'\"));\n    }\n\n    @Test\n    public void testFolders() {\n        // should add version if public_id contains /\n        String result = cloudinary.url().generate(\"folder/test\");\n        assertEquals(DEFAULT_UPLOAD_PATH + \"v1/folder/test\", result);\n        result = cloudinary.url().version(123).generate(\"folder/test\");\n        assertEquals(DEFAULT_UPLOAD_PATH + \"v123/folder/test\", result);\n    }\n\n    @Test\n    public void testFoldersWithExcludeVersion() {\n        // should not add version if the user turned off forceVersion\n        String result = cloudinary.url().forceVersion(false).generate(\"folder/test\");\n        assertEquals(DEFAULT_UPLOAD_PATH + \"folder/test\", result);\n\n        // should still show explicit version if passed by the user\n        result = cloudinary.url().forceVersion(false).version(\"1234\").generate(\"folder/test\");\n        assertEquals(DEFAULT_UPLOAD_PATH + \"v1234/folder/test\", result);\n\n        // should add version if no value specified for forceVersion:\n        result = cloudinary.url().generate(\"folder/test\");\n        assertEquals(DEFAULT_UPLOAD_PATH + \"v1/folder/test\", result);\n\n        // should not add version if the path STARTS with 'v[num]'\n        result = cloudinary.url().generate(\"v1234/folder/test\");\n        assertEquals(DEFAULT_UPLOAD_PATH + \"v1234/folder/test\", result);\n\n        // should add version if the path CONTAINS 'v[num]'\n        result = cloudinary.url().generate(\"folder/v123test\");\n        assertEquals(DEFAULT_UPLOAD_PATH + \"v1/folder/v123test\", result);\n\n        // should add version if forceVersion is true\n        result = cloudinary.url().forceVersion(true).generate(\"folder/test\");\n        assertEquals(DEFAULT_UPLOAD_PATH + \"v1/folder/test\", result);\n\n        // should not use v1 if explicit version is passed\n        result = cloudinary.url().forceVersion(true).version(\"1234\").generate(\"folder/test\");\n        assertEquals(DEFAULT_UPLOAD_PATH + \"v1234/folder/test\", result);\n    }\n\n    @Test\n    public void testFoldersWithVersion() {\n        // should not add version if public_id contains version already\n        String result = cloudinary.url().generate(\"v1234/test\");\n        assertEquals(DEFAULT_UPLOAD_PATH + \"v1234/test\", result);\n    }\n\n    @Test\n    public void testShorten() {\n        // should allow to shorted image/upload urls\n        String result = cloudinary.url().shorten(true).generate(\"test\");\n        assertEquals(\"https://res.cloudinary.com/test123/iu/test\", result);\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    @Test\n    public void testPrivateDownload() throws Exception {\n        long inTwentyMinutes = System.currentTimeMillis() / 1000 + 20 * 60;\n        String url = cloudinary.privateDownload(\"imgÿ=&é\", \"jpg\", Collections.<String, Object>singletonMap(\"expires_at\", inTwentyMinutes));\n        URI uri = new URI(url);\n        Map<String, String> parameters = getUrlParameters(uri);\n        assertEquals(\"imgÿ=&é\", parameters.get(\"public_id\"));\n        assertEquals(\"jpg\", parameters.get(\"format\"));\n        assertEquals(\"a\", parameters.get(\"api_key\"));\n        assertEquals(String.valueOf(inTwentyMinutes), parameters.get(\"expires_at\"));\n        assertEquals(\"/v1_1/test123/image/download\", uri.getPath());\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    @Test\n    public void testZipDownload() throws Exception {\n        String url = cloudinary.zipDownload(\"ttag\", emptyMap());\n        URI uri = new URI(url);\n        Map<String, String> parameters = getUrlParameters(uri);\n        assertEquals(\"ttag\", parameters.get(\"tag\"));\n        assertEquals(\"a\", parameters.get(\"api_key\"));\n        assertEquals(\"/v1_1/test123/image/download_tag.zip\", uri.getPath());\n    }\n\n    @Test\n    public void testDownloadSprite() throws Exception{\n        final String spriteTestTag = \"sprite_tag\";\n        final String url1 = \"https://res.cloudinary.com/demo/image/upload/sample\";\n        final String url2 = \"https://res.cloudinary.com/demo/image/upload/car\";\n\n        String urlFromTag = cloudinary.downloadGeneratedSprite(spriteTestTag, null);\n        String urlFromUrls = cloudinary.downloadGeneratedSprite(new String[]{url1, url2}, null);\n\n        assertTrue(urlFromTag.startsWith(\"https://api.cloudinary.com/v1_1/\" + cloudinary.config.cloudName + \"/image/sprite?mode=download\"));\n        assertTrue(urlFromUrls.startsWith(\"https://api.cloudinary.com/v1_1/\" + cloudinary.config.cloudName + \"/image/sprite?mode=download\"));\n        assertTrue(urlFromUrls.contains(\"urls[]=\" + URLEncoder.encode(url1, \"UTF-8\")));\n        assertTrue(urlFromUrls.contains(\"urls[]=\" + URLEncoder.encode(url2, \"UTF-8\")));\n\n        Map<String, String> parameters = getUrlParameters(new URI(urlFromTag));\n        assertEquals(spriteTestTag, parameters.get(\"tag\"));\n        assertNotNull(parameters.get(\"timestamp\"));\n        assertNotNull(parameters.get(\"signature\"));\n\n        parameters = getUrlParameters(new URI(urlFromUrls));\n        assertNotNull(parameters.get(\"timestamp\"));\n        assertNotNull(parameters.get(\"signature\"));\n    }\n\n    @Test\n    public void testDownloadMulti() throws Exception{\n        cloudinary = new Cloudinary(\"cloudinary://571927874334573:yABWqlfSV2d5pRW4ujHJYA7SD34@nitzanj?load_strategies=false\");\n\n        final String multiTestTag = \"multi_test_tag\";\n        final String url1 = \"https://res.cloudinary.com/demo/image/upload/sample\";\n        final String url2 = \"https://res.cloudinary.com/demo/image/upload/car\";\n\n        String urlFromTag = cloudinary.downloadMulti(multiTestTag, null);\n        String urlFromUrls = cloudinary.downloadMulti(new String[]{url1, url2}, null);\n\n        assertTrue(urlFromTag.startsWith(\"https://api.cloudinary.com/v1_1/\" + cloudinary.config.cloudName + \"/image/multi?mode=download\"));\n        assertTrue(urlFromUrls.startsWith(\"https://api.cloudinary.com/v1_1/\" + cloudinary.config.cloudName + \"/image/multi?mode=download\"));\n        assertTrue(urlFromUrls.contains(\"urls[]=\" + URLEncoder.encode(url1, \"UTF-8\")));\n        assertTrue(urlFromUrls.contains(\"urls[]=\" + URLEncoder.encode(url2, \"UTF-8\")));\n\n        Map<String, String> parameters = getUrlParameters(new URI(urlFromTag));\n        assertEquals(multiTestTag, parameters.get(\"tag\"));\n        assertNotNull(parameters.get(\"timestamp\"));\n        assertNotNull(parameters.get(\"signature\"));\n\n        parameters = getUrlParameters(new URI(urlFromUrls));\n        assertNotNull(parameters.get(\"timestamp\"));\n        assertNotNull(parameters.get(\"signature\"));\n\n    }\n\n    @Test\n    public void testDownloadFolderShouldReturnURLWithResourceTypeAllByDefault() throws UnsupportedEncodingException {\n        String url = cloudinary.downloadFolder(\"folder\", null);\n        assertTrue(url.contains(\"all\"));\n    }\n\n    @Test\n    public void testDownloadFolderShouldAllowToOverrideResourceType() throws UnsupportedEncodingException {\n        String url = cloudinary.downloadFolder(\"folder\", Collections.singletonMap(\"resource_type\", \"audio\"));\n        assertTrue(url.contains(\"audio\"));\n    }\n\n    @Test\n    public void testDownloadFolderShouldPutFolderPathAsPrefixes() throws UnsupportedEncodingException {\n        String url = cloudinary.downloadFolder(\"folder\", null);\n        assertTrue(url.contains(\"prefixes[]=folder\"));\n    }\n\n    @Test\n    public void testDownloadFolderShouldIncludeSpecifiedTargetFormat() throws UnsupportedEncodingException {\n        String url = cloudinary.downloadFolder(\"folder\", Collections.singletonMap(\"target_format\", \"rar\"));\n        assertTrue(url.contains(\"target_format=rar\"));\n    }\n\n    @Test\n    public void testDownloadFolderShouldNotIncludeTargetFormatIfNotSpecified() throws UnsupportedEncodingException {\n        String url = cloudinary.downloadFolder(\"folder\", null);\n        assertFalse(url.contains(\"target_format\"));\n    }\n\n    @Test\n    public void testSpriteCss() {\n        String result = cloudinary.url().generateSpriteCss(\"test\");\n        assertEquals(\"https://res.cloudinary.com/test123/image/sprite/test.css\", result);\n        result = cloudinary.url().generateSpriteCss(\"test.css\");\n        assertEquals(\"https://res.cloudinary.com/test123/image/sprite/test.css\", result);\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    @Test\n    public void testEscapePublicId() {\n        // should escape public_ids\n        Map<String, String> tests = asMap(\"a b\", \"a%20b\", \"a+b\", \"a%2Bb\", \"a%20b\", \"a%20b\", \"a-b\", \"a-b\", \"a??b\", \"a%3F%3Fb\");\n        for (Map.Entry<String, String> entry : tests.entrySet()) {\n            String result = cloudinary.url().generate(entry.getKey());\n            assertEquals(DEFAULT_UPLOAD_PATH + \"\" + entry.getValue(), result);\n        }\n    }\n\n    @Test\n    public void testSignedUrl() {\n        // should correctly sign a url\n        String expected = DEFAULT_UPLOAD_PATH + \"s--Ai4Znfl3--/c_crop,h_20,w_10/v1234/image.jpg\";\n        String actual = cloudinary.url().version(1234).transformation(new Transformation().crop(\"crop\").width(10).height(20)).signed(true)\n                .generate(\"image.jpg\");\n        assertEquals(expected, actual);\n\n        expected = DEFAULT_UPLOAD_PATH + \"s----SjmNDA--/v1234/image.jpg\";\n        actual = cloudinary.url().version(1234).signed(true).generate(\"image.jpg\");\n        assertEquals(expected, actual);\n\n        expected = DEFAULT_UPLOAD_PATH + \"s--Ai4Znfl3--/c_crop,h_20,w_10/image.jpg\";\n        actual = cloudinary.url().transformation(new Transformation().crop(\"crop\").width(10).height(20)).signed(true).generate(\"image.jpg\");\n        assertEquals(expected, actual);\n    }\n\n    @Test\n    public void testSignedUrlSHA256() {\n        cloudinary.config.signatureAlgorithm = SignatureAlgorithm.SHA256;\n\n        String url = cloudinary.url().signed(true).generate(\"sample.jpg\");\n        assertEquals(DEFAULT_UPLOAD_PATH + \"s--2hbrSMPO--/sample.jpg\", url);\n    }\n\n    @Test\n    public void testResponsiveWidth() {\n        // should support responsive width\n        Transformation trans = new Transformation().width(100).height(100).crop(\"crop\").responsiveWidth(true);\n        String result = cloudinary.url().transformation(trans).generate(\"test\");\n        assertTrue(trans.isResponsive());\n        assertEquals(DEFAULT_UPLOAD_PATH + \"c_crop,h_100,w_100/c_limit,w_auto/test\", result);\n        Transformation.setResponsiveWidthTransformation(asMap(\"width\", \"auto\", \"crop\", \"pad\"));\n        trans = new Transformation().width(100).height(100).crop(\"crop\").responsiveWidth(true);\n        result = cloudinary.url().transformation(trans).generate(\"test\");\n        assertTrue(trans.isResponsive());\n        assertEquals(DEFAULT_UPLOAD_PATH + \"c_crop,h_100,w_100/c_pad,w_auto/test\", result);\n        Transformation.setResponsiveWidthTransformation(null);\n    }\n\n    @Parameters({\n            \"auto:20|c_fill\\\\,w_auto:20\",\n            \"auto:20:350|c_fill\\\\,w_auto:20:350\",\n            \"auto:breakpoints|c_fill\\\\,w_auto:breakpoints\",\n            \"auto:breakpoints_100_1900_20_15|c_fill\\\\,w_auto:breakpoints_100_1900_20_15\",\n            \"auto:breakpoints:json|c_fill\\\\,w_auto:breakpoints:json\"})\n    @TestCaseName(\"Width {0}: {1}\")\n    @Test\n    public void testShouldSupportAutoWidth(String width, String result) {\n        String trans;\n        trans = new Transformation().width(width).crop(\"fill\").generate();\n        assertEquals(result, trans);\n    }\n\n    @Test\n    public void testEagerWithStreamingProfile() throws IOException {\n        Transformation transformation = new EagerTransformation().format(\"m3u8\").streamingProfile(\"full_hd\");\n        assertEquals(\"sp_full_hd/m3u8\", transformation.generate());\n    }\n\n    @Test\n    public void testEagerWithChaining() throws IOException {\n        Transformation transformation = new EagerTransformation().angle(13).chain().effect(\"sepia\").chain().format(\"webp\");\n        assertEquals(\"a_13/e_sepia/webp\", transformation.generate());\n    }\n\n    @Test\n    public void testShouldSupportIhIw() {\n        String trans = new Transformation().width(\"iw\").height(\"ih\").crop(\"crop\").generate();\n        assertEquals(\"c_crop,h_ih,w_iw\", trans);\n    }\n\n    @Test\n    public void testVideoCodec() {\n        // should support a string value\n        String actual = cloudinary.url().resourceType(\"video\").transformation(new Transformation().videoCodec(\"auto\"))\n                .generate(\"video_id\");\n        assertEquals(VIDEO_UPLOAD_PATH + \"vc_auto/video_id\", actual);\n        // should support a hash value\n        actual = cloudinary.url().resourceType(\"video\")\n                .transformation(\n                        new Transformation().videoCodec(asMap(\"codec\", \"h264\", \"profile\", \"basic\", \"level\", \"3.1\"))\n                ).generate(\"video_id\");\n        assertEquals(VIDEO_UPLOAD_PATH + \"vc_h264:basic:3.1/video_id\", actual);\n    }\n\n    @Test\n    public void testVideoCodecBFrameTrue() {\n        String actual = cloudinary.url().resourceType(\"video\")\n                .transformation(\n                        new Transformation().videoCodec(asMap(\"codec\", \"h264\", \"profile\", \"basic\", \"level\", \"3.1\", \"b_frames\", \"true\"))\n                ).generate(\"video_id\");\n        assertEquals(VIDEO_UPLOAD_PATH + \"vc_h264:basic:3.1/video_id\", actual);\n    }\n\n    @Test\n    public void testVideoCodecBFrameFalse() {\n        String actual = cloudinary.url().resourceType(\"video\")\n                .transformation(\n                        new Transformation().videoCodec(asMap(\"codec\", \"h264\", \"profile\", \"basic\", \"level\", \"3.1\", \"b_frames\", \"false\"))\n                ).generate(\"video_id\");\n        assertEquals(VIDEO_UPLOAD_PATH + \"vc_h264:basic:3.1:bframes_no/video_id\", actual);\n    }\n\n    @Test\n    public void testAudioCodec() {\n        // should support a string value\n        String actual = cloudinary.url().resourceType(\"video\").transformation(new Transformation().audioCodec(\"acc\")).generate(\"video_id\");\n        assertEquals(VIDEO_UPLOAD_PATH + \"ac_acc/video_id\", actual);\n    }\n\n    @Test\n    public void testBitRate() {\n        // should support a numeric value\n        String actual = cloudinary.url().resourceType(\"video\").transformation(new Transformation().bitRate(2048))\n                .generate(\"video_id\");\n        assertEquals(VIDEO_UPLOAD_PATH + \"br_2048/video_id\", actual);\n        // should support a string value\n        actual = cloudinary.url().resourceType(\"video\").transformation(new Transformation().bitRate(\"44k\"))\n                .generate(\"video_id\");\n        assertEquals(VIDEO_UPLOAD_PATH + \"br_44k/video_id\", actual);\n        actual = cloudinary.url().resourceType(\"video\").transformation(new Transformation().bitRate(\"1m\"))\n                .generate(\"video_id\");\n        assertEquals(VIDEO_UPLOAD_PATH + \"br_1m/video_id\", actual);\n\n    }\n\n    @Test\n    public void testAudioFrequency() {\n        // should support an integer value\n        String actual = cloudinary.url().resourceType(\"video\")\n                .transformation(new Transformation().audioFrequency(44100)).generate(\"video_id\");\n        assertEquals(VIDEO_UPLOAD_PATH + \"af_44100/video_id\", actual);\n        // should support a string value\n        actual = cloudinary.url().resourceType(\"video\").transformation(new Transformation().audioFrequency(\"44100\"))\n                .generate(\"video_id\");\n        assertEquals(VIDEO_UPLOAD_PATH + \"af_44100/video_id\", actual);\n    }\n\n    @Test\n    public void testVideoSampling() {\n        String actual = cloudinary.url().resourceType(\"video\")\n                .transformation(new Transformation().videoSamplingFrames(20)).generate(\"video_id\");\n        assertEquals(VIDEO_UPLOAD_PATH + \"vs_20/video_id\", actual);\n        actual = cloudinary.url().resourceType(\"video\").transformation(new Transformation().videoSamplingSeconds(20))\n                .generate(\"video_id\");\n        assertEquals(VIDEO_UPLOAD_PATH + \"vs_20s/video_id\", actual);\n        actual = cloudinary.url().resourceType(\"video\").transformation(new Transformation().videoSamplingSeconds(20.0))\n                .generate(\"video_id\");\n        assertEquals(VIDEO_UPLOAD_PATH + \"vs_20.0s/video_id\", actual);\n        actual = cloudinary.url().resourceType(\"video\").transformation(new Transformation().videoSampling(\"2.3s\"))\n                .generate(\"video_id\");\n        assertEquals(VIDEO_UPLOAD_PATH + \"vs_2.3s/video_id\", actual);\n    }\n\n    @Test\n    public void testStartOffset() {\n        String actual = cloudinary.url().resourceType(\"video\").transformation(new Transformation().startOffset(2.63))\n                .generate(\"video_id\");\n        assertEquals(VIDEO_UPLOAD_PATH + \"so_2.63/video_id\", actual);\n        actual = cloudinary.url().resourceType(\"video\").transformation(new Transformation().startOffset(\"2.63p\"))\n                .generate(\"video_id\");\n        assertEquals(VIDEO_UPLOAD_PATH + \"so_2.63p/video_id\", actual);\n        actual = cloudinary.url().resourceType(\"video\").transformation(new Transformation().startOffset(\"2.63%\"))\n                .generate(\"video_id\");\n        assertEquals(VIDEO_UPLOAD_PATH + \"so_2.63p/video_id\", actual);\n        actual = cloudinary.url().resourceType(\"video\").transformation(new Transformation().startOffsetPercent(2.63))\n                .generate(\"video_id\");\n        assertEquals(VIDEO_UPLOAD_PATH + \"so_2.63p/video_id\", actual);\n        actual = cloudinary.url().resourceType(\"video\").transformation(new Transformation().startOffset(\"auto\"))\n                .generate(\"video_id\");\n        assertEquals(VIDEO_UPLOAD_PATH + \"so_auto/video_id\", actual);\n    }\n\n    @Test\n    public void testDuration() {\n        String actual = cloudinary.url().resourceType(\"video\").transformation(new Transformation().duration(2.63))\n                .generate(\"video_id\");\n        assertEquals(VIDEO_UPLOAD_PATH + \"du_2.63/video_id\", actual);\n        actual = cloudinary.url().resourceType(\"video\").transformation(new Transformation().duration(\"2.63p\"))\n                .generate(\"video_id\");\n        assertEquals(VIDEO_UPLOAD_PATH + \"du_2.63p/video_id\", actual);\n        actual = cloudinary.url().resourceType(\"video\").transformation(new Transformation().duration(\"2.63%\"))\n                .generate(\"video_id\");\n        assertEquals(VIDEO_UPLOAD_PATH + \"du_2.63p/video_id\", actual);\n        actual = cloudinary.url().resourceType(\"video\").transformation(new Transformation().durationPercent(2.63))\n                .generate(\"video_id\");\n        assertEquals(VIDEO_UPLOAD_PATH + \"du_2.63p/video_id\", actual);\n    }\n\n    @Test\n    public void testOffset() {\n\n        String actual = cloudinary.url().resourceType(\"video\")\n                .transformation(new Transformation().offset(\"2.66..3.21\")).generate(\"video_id\");\n        assertEquals(VIDEO_UPLOAD_PATH + \"eo_3.21,so_2.66/video_id\", actual);\n        actual = cloudinary.url().resourceType(\"video\")\n                .transformation(new Transformation().offset(new float[]{2.67f, 3.22f})).generate(\"video_id\");\n        assertEquals(VIDEO_UPLOAD_PATH + \"eo_3.22,so_2.67/video_id\", actual);\n        actual = cloudinary.url().resourceType(\"video\")\n                .transformation(new Transformation().offset(new double[]{2.67, 3.22})).generate(\"video_id\");\n        assertEquals(VIDEO_UPLOAD_PATH + \"eo_3.22,so_2.67/video_id\", actual);\n        actual = cloudinary.url().resourceType(\"video\")\n                .transformation(new Transformation().offset(new String[]{\"35%\", \"70%\"})).generate(\"video_id\");\n        assertEquals(VIDEO_UPLOAD_PATH + \"eo_70p,so_35p/video_id\", actual);\n        actual = cloudinary.url().resourceType(\"video\")\n                .transformation(new Transformation().offset(new String[]{\"36p\", \"71p\"})).generate(\"video_id\");\n        assertEquals(VIDEO_UPLOAD_PATH + \"eo_71p,so_36p/video_id\", actual);\n        actual = cloudinary.url().resourceType(\"video\")\n                .transformation(new Transformation().offset(new String[]{\"35.5p\", \"70.5p\"})).generate(\"video_id\");\n        assertEquals(VIDEO_UPLOAD_PATH + \"eo_70.5p,so_35.5p/video_id\", actual);\n\n    }\n\n    @Test\n    public void testZoom() {\n        String actual = cloudinary.url().resourceType(\"video\").transformation(new Transformation().zoom(\"1.5\"))\n                .generate(\"video_id\");\n        assertEquals(VIDEO_UPLOAD_PATH + \"z_1.5/video_id\", actual);\n        actual = cloudinary.url().resourceType(\"video\").transformation(new Transformation().zoom(1.5))\n                .generate(\"video_id\");\n        assertEquals(VIDEO_UPLOAD_PATH + \"z_1.5/video_id\", actual);\n    }\n\n    @Test\n    public void testUtils() {\n        assertEquals(ObjectUtils.asBoolean(true, null), true);\n        assertEquals(ObjectUtils.asBoolean(false, null), false);\n    }\n\n    @Test\n    public void testVideoTag() {\n        String expectedUrl = VIDEO_UPLOAD_PATH + \"movie\";\n        String expectedTag = \"<video poster='%s.jpg'>\" + \"<source src='%s.webm' type='video/webm'>\"\n                + \"<source src='%s.mp4' type='video/mp4'>\"\n                + \"<source src='%s.ogv' type='video/ogg'>\"\n                + \"</video>\";\n        expectedTag = String.format(expectedTag, expectedUrl, expectedUrl, expectedUrl, expectedUrl);\n        assertEquals(expectedTag, cloudinary.url().videoTag(\"movie\", emptyMap()));\n        assertEquals(expectedTag, cloudinary.url().publicId(\"movie\").videoTag());\n        assertEquals(expectedTag, cloudinary.url().videoTag(\"movie\"));\n    }\n\n    @Test\n    public void testVideoTagWithAttributes() {\n        Map attributes = asMap(\n                \"autoplay\", true,\n                \"controls\", null,\n                \"loop\", null,\n                \"muted\", \"true\",\n                \"preload\", null,\n                \"style\", \"border: 1px\");\n        String expectedUrl = VIDEO_UPLOAD_PATH + \"movie\";\n        String expectedTag = \"<video autoplay='true' controls loop muted='true' poster='%s.jpg' preload style='border: 1px'>\"\n                + \"<source src='%s.webm' type='video/webm'>\"\n                + \"<source src='%s.mp4' type='video/mp4'>\"\n                + \"<source src='%s.ogv' type='video/ogg'>\" + \"</video>\";\n        expectedTag = String.format(expectedTag, expectedUrl, expectedUrl, expectedUrl, expectedUrl);\n        assertEquals(expectedTag, cloudinary.url().videoTag(\"movie\", attributes));\n    }\n\n    @Test\n    public void testVideoTagWithTransformation() {\n        Transformation transformation = new Transformation().videoCodec(asMap(\"codec\", \"h264\"))\n                .audioCodec(\"acc\").startOffset(3);\n        String expectedUrl = VIDEO_UPLOAD_PATH + \"ac_acc,so_3.0,vc_h264/movie\";\n        String expectedTag = \"<video height='100' poster='%s.jpg' src='%s.mp4' width='200'></video>\";\n        expectedTag = String.format(expectedTag, expectedUrl, expectedUrl);\n        String actualTag = cloudinary.url().transformation(transformation).sourceTypes(new String[]{\"mp4\"})\n                .videoTag(\"movie\", asMap(\"html_height\", \"100\", \"html_width\", \"200\"));\n        assertEquals(expectedTag, actualTag);\n\n        expectedTag = \"<video height='100' poster='%s.jpg' width='200'>\"\n                + \"<source src='%s.webm' type='video/webm'>\"\n                + \"<source src='%s.mp4' type='video/mp4'>\"\n                + \"<source src='%s.ogv' type='video/ogg'>\"\n                + \"</video>\";\n        expectedTag = String.format(expectedTag, expectedUrl, expectedUrl, expectedUrl, expectedUrl);\n        actualTag = cloudinary.url().transformation(transformation)\n                .videoTag(\"movie\", asMap(\"html_height\", \"100\", \"html_width\", \"200\"));\n        assertEquals(expectedTag, actualTag);\n\n        transformation.width(250);\n        expectedUrl = VIDEO_UPLOAD_PATH + \"ac_acc,so_3.0,vc_h264,w_250/movie\";\n        expectedTag = \"<video poster='%s.jpg' width='250'>\"\n                + \"<source src='%s.webm' type='video/webm'>\"\n                + \"<source src='%s.mp4' type='video/mp4'>\"\n                + \"<source src='%s.ogv' type='video/ogg'>\"\n                + \"</video>\";\n        expectedTag = String.format(expectedTag, expectedUrl, expectedUrl, expectedUrl, expectedUrl);\n        actualTag = cloudinary.url().transformation(transformation)\n                .videoTag(\"movie\", asMap());\n        assertEquals(expectedTag, actualTag);\n\n        transformation.crop(\"fit\");\n        expectedUrl = VIDEO_UPLOAD_PATH + \"ac_acc,c_fit,so_3.0,vc_h264,w_250/movie\";\n        expectedTag = \"<video poster='%s.jpg'>\"\n                + \"<source src='%s.webm' type='video/webm'>\"\n                + \"<source src='%s.mp4' type='video/mp4'>\"\n                + \"<source src='%s.ogv' type='video/ogg'>\"\n                + \"</video>\";\n        expectedTag = String.format(expectedTag, expectedUrl, expectedUrl, expectedUrl, expectedUrl);\n        actualTag = cloudinary.url().transformation(transformation)\n                .videoTag(\"movie\", asMap());\n        assertEquals(expectedTag, actualTag);\n    }\n\n    @Test\n    public void testVideoTagWithFallback() {\n        String expectedUrl = VIDEO_UPLOAD_PATH + \"movie\";\n        String fallback = \"<span id='spanid'>Cannot display video</span>\";\n        String expectedTag = \"<video poster='%s.jpg' src='%s.mp4'>%s</video>\";\n        expectedTag = String.format(expectedTag, expectedUrl, expectedUrl, fallback);\n        String actualTag = cloudinary.url().fallbackContent(fallback).sourceTypes(new String[]{\"mp4\"})\n                .videoTag(\"movie\", emptyMap());\n        assertEquals(expectedTag, actualTag);\n\n        expectedTag = \"<video poster='%s.jpg'>\" + \"<source src='%s.webm' type='video/webm'>\"\n                + \"<source src='%s.mp4' type='video/mp4'>\" + \"<source src='%s.ogv' type='video/ogg'>%s\" + \"</video>\";\n        expectedTag = String.format(expectedTag, expectedUrl, expectedUrl, expectedUrl, expectedUrl, fallback);\n        actualTag = cloudinary.url().fallbackContent(fallback).videoTag(\"movie\", emptyMap());\n        assertEquals(expectedTag, actualTag);\n    }\n\n    @Test\n    public void testVideoTagWithSourceTypes() {\n        String expectedUrl = VIDEO_UPLOAD_PATH + \"movie\";\n        String expectedTag = \"<video poster='%s.jpg'>\" + \"<source src='%s.ogv' type='video/ogg'>\"\n                + \"<source src='%s.mp4' type='video/mp4'>\" + \"</video>\";\n        expectedTag = String.format(expectedTag, expectedUrl, expectedUrl, expectedUrl);\n        String actualTag = cloudinary.url().sourceTypes(new String[]{\"ogv\", \"mp4\"})\n                .videoTag(\"movie.mp4\", emptyMap());\n        assertEquals(expectedTag, actualTag);\n    }\n\n    @Test\n    public void testVideoTagWithSourceTransformation() {\n        String expectedUrl = VIDEO_UPLOAD_PATH + \"q_50/w_100/movie\";\n        String expectedOgvUrl = VIDEO_UPLOAD_PATH + \"q_50/w_100/q_70/movie\";\n        String expectedMp4Url = VIDEO_UPLOAD_PATH + \"q_50/w_100/q_30/movie\";\n        String expectedTag = \"<video poster='%s.jpg' width='100'>\"\n                + \"<source src='%s.webm' type='video/webm'>\"\n                + \"<source src='%s.mp4' type='video/mp4'>\"\n                + \"<source src='%s.ogv' type='video/ogg'>\"\n                + \"</video>\";\n        expectedTag = String.format(expectedTag, expectedUrl, expectedUrl, expectedMp4Url, expectedOgvUrl);\n        String actualTag = cloudinary.url().transformation(new Transformation().quality(50).chain().width(100))\n                .sourceTransformationFor(\"mp4\", new Transformation().quality(30))\n                .sourceTransformationFor(\"ogv\", new Transformation().quality(70))\n                .videoTag(\"movie\", emptyMap());\n        assertEquals(expectedTag, actualTag);\n\n        expectedTag = \"<video poster='%s.jpg' width='100'>\" + \"<source src='%s.webm' type='video/webm'>\"\n                + \"<source src='%s.mp4' type='video/mp4'>\" + \"</video>\";\n        expectedTag = String.format(expectedTag, expectedUrl, expectedUrl, expectedMp4Url);\n        actualTag = cloudinary.url().transformation(new Transformation().quality(50).chain().width(100))\n                .sourceTransformationFor(\"mp4\", new Transformation().quality(30))\n                .sourceTransformationFor(\"ogv\", new Transformation().quality(70))\n                .sourceTypes(new String[]{\"webm\", \"mp4\"}).videoTag(\"movie\", emptyMap());\n        assertEquals(expectedTag, actualTag);\n    }\n\n    @Test\n    public void testVideoTagWithPoster() {\n        String expectedUrl = VIDEO_UPLOAD_PATH + \"movie\";\n        String posterUrl = \"http://image/somewhere.jpg\";\n        String expectedTag = \"<video poster='%s' src='%s.mp4'></video>\";\n        expectedTag = String.format(expectedTag, posterUrl, expectedUrl);\n        String actualTag = cloudinary.url().sourceTypes(new String[]{\"mp4\"}).poster(posterUrl)\n                .videoTag(\"movie\", emptyMap());\n        assertEquals(expectedTag, actualTag);\n\n        posterUrl = VIDEO_UPLOAD_PATH + \"g_north/movie.jpg\";\n        expectedTag = \"<video poster='%s' src='%s.mp4'></video>\";\n        expectedTag = String.format(expectedTag, posterUrl, expectedUrl);\n        actualTag = cloudinary.url().sourceTypes(new String[]{\"mp4\"})\n                .poster(new Transformation().gravity(\"north\"))\n                .videoTag(\"movie\", emptyMap());\n        assertEquals(expectedTag, actualTag);\n\n        posterUrl = DEFAULT_UPLOAD_PATH + \"g_north/my_poster.jpg\";\n        expectedTag = \"<video poster='%s' src='%s.mp4'></video>\";\n        expectedTag = String.format(expectedTag, posterUrl, expectedUrl);\n        actualTag = cloudinary.url().sourceTypes(new String[]{\"mp4\"})\n                .poster(cloudinary.url()\n                        .publicId(\"my_poster\")\n                        .format(\"jpg\")\n                        .transformation(new Transformation().gravity(\"north\")))\n                .videoTag(\"movie\", emptyMap());\n        assertEquals(expectedTag, actualTag);\n\n        expectedTag = \"<video src='%s.mp4'></video>\";\n        expectedTag = String.format(expectedTag, expectedUrl);\n        actualTag = cloudinary.url().sourceTypes(new String[]{\"mp4\"})\n                .poster(null)\n                .videoTag(\"movie\", emptyMap());\n        assertEquals(expectedTag, actualTag);\n\n        actualTag = cloudinary.url().sourceTypes(new String[]{\"mp4\"})\n                .poster(false)\n                .videoTag(\"movie\", emptyMap());\n        assertEquals(expectedTag, actualTag);\n    }\n\n    @Test\n    public void videoTagWithAuthTokenTest() {\n        String actualTag = cloudinary.url().transformation(new Transformation())\n                .type(\"upload\")\n                .authToken(new AuthToken(\"123456\").duration(300))\n                .signed(true)\n                .secure(true)\n                .videoTag(\"sample\", ObjectUtils.asMap(\n                        \"controls\", true,\n                        \"loop\", true)\n                );\n        assert(actualTag.contains(\"cld_token\"));\n    }\n\n    @Test\n    public void testAspectRatio() {\n        String actual = cloudinary.url().transformation(new Transformation().aspectRatio(\"1.5\"))\n                .generate(\"test\");\n        assertEquals(DEFAULT_UPLOAD_PATH + \"ar_1.5/test\", actual);\n        actual = cloudinary.url().transformation(new Transformation().aspectRatio(1.5))\n                .generate(\"test\");\n        assertEquals(DEFAULT_UPLOAD_PATH + \"ar_1.5/test\", actual);\n        actual = cloudinary.url().transformation(new Transformation().aspectRatio(3, 2))\n                .generate(\"test\");\n        assertEquals(DEFAULT_UPLOAD_PATH + \"ar_3:2/test\", actual);\n    }\n\n    @Test\n    public void testOverlayOptions() {\n        Object tests[] = {\n                new Layer().publicId(\"logo\"),\n                \"logo\",\n                new Layer().publicId(\"folder/logo\"),\n                \"folder:logo\",\n                new Layer().publicId(\"logo\").type(\"private\"),\n                \"private:logo\",\n                new Layer().publicId(\"logo\").format(\"png\"),\n                \"logo.png\",\n                new Layer().resourceType(\"video\").publicId(\"cat\"),\n                \"video:cat\",\n                new TextLayer().text(\"Hello/World\").fontFamily(\"Arial\").fontSize(18),\n                \"text:Arial_18:Hello%252FWorld\",\n                new TextLayer().text(\"Hello World, Nice to meet you?\").fontFamily(\"Arial\").fontSize(18),\n                \"text:Arial_18:Hello%20World%252C%20Nice%20to%20meet%20you%3F\",\n                new TextLayer().text(\"Hello World, Nice to meet you?\").fontFamily(\"Arial\").fontSize(18)\n                        .fontWeight(\"bold\").fontStyle(\"italic\").letterSpacing(\"4\").lineSpacing(3),\n                \"text:Arial_18_bold_italic_letter_spacing_4_line_spacing_3:Hello%20World%252C%20Nice%20to%20meet%20you%3F\",\n                new TextLayer().text(\"Hello World, Nice to meet you?\").fontFamily(\"Arial\").fontSize(18)\n                        .fontWeight(\"bold\").fontStyle(\"italic\").letterSpacing(4).lineSpacing(3),\n                \"text:Arial_18_bold_italic_letter_spacing_4_line_spacing_3:Hello%20World%252C%20Nice%20to%20meet%20you%3F\",\n                new TextLayer().text(\"Hello World, Nice to meet you?\").fontFamily(\"Arial\").fontSize(18)\n                        .fontAntialiasing(\"best\").fontHinting(\"medium\"),\n                \"text:Arial_18_antialias_best_hinting_medium:Hello%20World%252C%20Nice%20to%20meet%20you%3F\",\n                new SubtitlesLayer().publicId(\"sample_sub_en.srt\"), \"subtitles:sample_sub_en.srt\",\n                new SubtitlesLayer().publicId(\"sample_sub_he.srt\").fontFamily(\"Arial\").fontSize(40),\n                \"subtitles:Arial_40:sample_sub_he.srt\",\n                new FetchLayer().url(\"https://test\").resourceType(\"image\"),\n                \"fetch:aHR0cHM6Ly90ZXN0\",\n                new FetchLayer().url(\"https://test\"),\n                \"fetch:aHR0cHM6Ly90ZXN0\",\n                new FetchLayer().url(\"https://test\").resourceType(\"video\"),\n                \"video:fetch:aHR0cHM6Ly90ZXN0\",\n                new FetchLayer().url(\"https://www.test.com/test/JE01118-YGP900_1_lar.jpg?version=432023\"),\n                \"fetch:aHR0cHM6Ly93d3cudGVzdC5jb20vdGVzdC9KRTAxMTE4LVlHUDkwMF8xX2xhci5qcGc_dmVyc2lvbj00MzIwMjM=\"\n        };\n\n        for (int i = 0; i < tests.length; i += 2) {\n            Object layer = tests[i];\n            String expected = (String) tests[i + 1];\n            assertEquals(expected, layer.toString());\n        }\n    }\n\n    @Test\n    @SuppressWarnings(\"deprecation\")\n    public void testBackwardCampatibleOverlayOptions() {\n        Object tests[] = {\n                new Layer().publicId(\"logo\"),\n                \"logo\",\n                new Layer().publicId(\"folder/logo\"),\n                \"folder:logo\",\n                new Layer().publicId(\"logo\").type(\"private\"),\n                \"private:logo\",\n                new Layer().publicId(\"logo\").format(\"png\"),\n                \"logo.png\",\n                new Layer().resourceType(\"video\").publicId(\"cat\"),\n                \"video:cat\",\n                new TextLayer().text(\"Hello/World\").fontFamily(\"Arial\").fontSize(18),\n                \"text:Arial_18:Hello%252FWorld\",\n                new TextLayer().text(\"Hello World, Nice to meet you?\").fontFamily(\"Arial\").fontSize(18),\n                \"text:Arial_18:Hello%20World%252C%20Nice%20to%20meet%20you%3F\",\n                new TextLayer().text(\"Hello World, Nice to meet you?\").fontFamily(\"Arial\").fontSize(18)\n                        .fontWeight(\"bold\").fontStyle(\"italic\").letterSpacing(\"4\"),\n                \"text:Arial_18_bold_italic_letter_spacing_4:Hello%20World%252C%20Nice%20to%20meet%20you%3F\",\n                new SubtitlesLayer().publicId(\"sample_sub_en.srt\"), \"subtitles:sample_sub_en.srt\",\n                new SubtitlesLayer().publicId(\"sample_sub_he.srt\").fontFamily(\"Arial\").fontSize(40),\n                \"subtitles:Arial_40:sample_sub_he.srt\"};\n\n        for (int i = 0; i < tests.length; i += 2) {\n            Object layer = tests[i];\n            String expected = (String) tests[i + 1];\n            assertEquals(expected, layer.toString());\n        }\n    }\n\n    @Test(expected = IllegalArgumentException.class)\n    public void testOverlayError1() {\n        // Must supply font_family for text in overlay\n        cloudinary.url().transformation(new Transformation().overlay(new TextLayer().fontStyle(\"italic\"))).generate(\"test\");\n    }\n\n    @Test(expected = IllegalArgumentException.class)\n    public void testOverlayError2() {\n        // Must supply public_id for for non-text underlay\n        cloudinary.url().transformation(new Transformation().underlay(new Layer().resourceType(\"video\"))).generate(\"test\");\n    }\n\n    @Test\n    public void testResponsiveBreakpointsToJson() {\n        assertEquals(\"an empty ResponsiveBreakpoint should have create_derived=true\",\n                \"{\\\"create_derived\\\":true}\",\n                new ResponsiveBreakpoint().toString()\n        );\n        String[] expectedArr = \"{\\\"create_derived\\\":false,\\\"max_width\\\":500,\\\"min_width\\\":100,\\\"max_images\\\":5,\\\"transformation\\\":\\\"a_45\\\"}\".split(\"[{}]\")[1].split(\",(?=\\\")\");\n        Arrays.sort(expectedArr);\n        JSONObject actual = new ResponsiveBreakpoint().createDerived(false)\n                .transformation(new Transformation().angle(45))\n                .maxWidth(500)\n                .minWidth(100)\n                .maxImages(5);\n        String[] actualArr = actual.toString().split(\"[{}]\")[1].split(\",(?=\\\")\");\n        Arrays.sort(actualArr);\n        assertArrayEquals(expectedArr, actualArr);\n    }\n\n    @Test\n    public void testFps() {\n        Transformation t = new Transformation().fps(\"24-29.97\");\n        assertEquals(\"fps_24-29.97\", t.generate());\n        t = new Transformation().fps(24);\n        assertEquals(\"fps_24\", t.generate());\n        t = new Transformation().fps(24.5);\n        assertEquals(\"fps_24.5\", t.generate());\n        t = new Transformation().fps(\"24\");\n        assertEquals(\"fps_24\", t.generate());\n        t = new Transformation().fps(\"-24\");\n        assertEquals(\"fps_-24\", t.generate());\n        t = new Transformation().fps(24, 29.97);\n        assertEquals(\"fps_24-29.97\", t.generate());\n        t = new Transformation().fps(24, null);\n        assertEquals(\"fps_24-\", t.generate());\n        t = new Transformation().fps(null, 29.97);\n        assertEquals(\"fps_-29.97\", t.generate());\n    }\n\n    @Test\n    public void testKeyframeInterval() {\n        assertEquals(\"ki_10.0\", new Transformation().keyframeInterval(10).generate());\n        assertEquals(\"ki_0.05\", new Transformation().keyframeInterval(0.05f).generate());\n        assertEquals(\"ki_3.45\", new Transformation().keyframeInterval(3.45f).generate());\n        assertEquals(\"ki_300.0\", new Transformation().keyframeInterval(300).generate());\n        assertEquals(\"ki_10\", new Transformation().keyframeInterval(\"10\").generate());\n        assertEquals(\"\", new Transformation().keyframeInterval(\"\").generate());\n        assertEquals(\"\", new Transformation().keyframeInterval(null).generate());\n    }\n\n    @Test\n    public void testCustomFunction() {\n        assertEquals(\"fn_wasm:blur_wasm\", new Transformation().customFunction(wasm(\"blur_wasm\")).generate());\n        assertEquals(\"fn_remote:aHR0cHM6Ly9kZjM0cmE0YS5leGVjdXRlLWFwaS51cy13ZXN0LTIuYW1hem9uYXdzLmNvbS9kZWZhdWx0L2Nsb3VkaW5hcnlGdW5jdGlvbg==\",\n                new Transformation().customFunction(remote(\"https://df34ra4a.execute-api.us-west-2.amazonaws.com/default/cloudinaryFunction\")).generate());\n    }\n\n    @Test\n    public void testCustomPreFunction() {\n        assertEquals(\"fn_pre:wasm:blur_wasm\", new Transformation().customPreFunction(wasm(\"blur_wasm\")).generate());\n        assertEquals(\"fn_pre:remote:aHR0cHM6Ly9kZjM0cmE0YS5leGVjdXRlLWFwaS51cy13ZXN0LTIuYW1hem9uYXdzLmNvbS9kZWZhdWx0L2Nsb3VkaW5hcnlGdW5jdGlvbg==\",\n                new Transformation().customPreFunction(remote(\"https://df34ra4a.execute-api.us-west-2.amazonaws.com/default/cloudinaryFunction\")).generate());\n    }\n\n    public static Map<String, String> getUrlParameters(URI uri) throws UnsupportedEncodingException {\n        Map<String, String> params = new HashMap<String, String>();\n        for (String param : uri.getRawQuery().split(\"&\")) {\n            String pair[] = param.split(\"=\");\n            String key = URLDecoder.decode(pair[0], \"UTF-8\");\n            String value = \"\";\n            if (pair.length > 1) {\n                value = URLDecoder.decode(pair[1], \"UTF-8\");\n            }\n            params.put(key, value);\n        }\n        return params;\n    }\n\n    @Test\n    public void testUrlCloneConfig() {\n        // verify that secure (from url.config) is cloned as well:\n        Url url = cloudinary.url().cloudName(\"cloud\").format(\"frmt\").publicId(\"123\");\n        assertEquals(\"https://res.cloudinary.com/cloud/image/upload/123.frmt\", url.clone().generate());\n    }\n\n    @Test\n    public void testConfiguration() throws IllegalAccessException {\n        Configuration config = new Configuration();\n        randomizeFields(config);\n        Map<String, Object> map = config.asMap();\n        Configuration copy = new Configuration(map);\n        assertFieldsEqual(config, copy);\n\n        copy = new Configuration(config);\n        assertFieldsEqual(config, copy);\n    }\n\n    @Test\n    public void testCloudinaryUrlValidScheme() {\n        String cloudinaryUrl = \"cloudinary://123456789012345:ALKJdjklLJAjhkKJ45hBK92baj3@test\";\n        Configuration.from(cloudinaryUrl);\n    }\n\n    @Test(expected = IllegalArgumentException.class)\n    public void testCloudinaryUrlInvalidScheme() {\n        String cloudinaryUrl = \"https://123456789012345:ALKJdjklLJAjhkKJ45hBK92baj3@test\";\n        Configuration.from(cloudinaryUrl);\n    }\n\n    @Test(expected = IllegalArgumentException.class)\n    public void testCloudinaryUrlEmptyScheme() {\n        String cloudinaryUrl = \" \";\n        Configuration.from(cloudinaryUrl);\n    }\n\n    @Test\n    public void testApiSignRequestSHA1() {\n        cloudinary.config.signatureAlgorithm = SignatureAlgorithm.SHA1;\n        String signature = cloudinary.apiSignRequest(ObjectUtils.asMap(\"cloud_name\", \"dn6ot3ged\", \"timestamp\", 1568810420, \"username\", \"user@cloudinary.com\"), \"hdcixPpR2iKERPwqvH6sHdK9cyac\", cloudinary.config.signatureVersion);\n        assertEquals(\"14c00ba6d0dfdedbc86b316847d95b9e6cd46d94\", signature);\n    }\n\n    @Test\n    public void testApiSignRequestSHA256() {\n        cloudinary.config.signatureAlgorithm = SignatureAlgorithm.SHA256;\n        String signature = cloudinary.apiSignRequest(ObjectUtils.asMap(\"cloud_name\", \"dn6ot3ged\", \"timestamp\", 1568810420, \"username\", \"user@cloudinary.com\"), \"hdcixPpR2iKERPwqvH6sHdK9cyac\", cloudinary.config.signatureVersion);\n        assertEquals(\"45ddaa4fa01f0c2826f32f669d2e4514faf275fe6df053f1a150e7beae58a3bd\", signature);\n    }\n\n    @Test\n    public void testDownloadBackedupAsset() throws UnsupportedEncodingException, URISyntaxException {\n        String url = cloudinary.downloadBackedupAsset(\"62c2a18d622be7e190d21df8e05b1416\",\n                \"26fe6d95df856f6ae12f5678be94516a\", ObjectUtils.emptyMap());\n\n        URI uri = new URI(url);\n        assertTrue(uri.getPath().endsWith(\"download_backup\"));\n\n        Map params = getUrlParameters(uri);\n        assertEquals(\"62c2a18d622be7e190d21df8e05b1416\", params.get(\"asset_id\"));\n        assertEquals(\"26fe6d95df856f6ae12f5678be94516a\", params.get(\"version_id\"));\n        assertNotNull(params.get(\"signature\"));\n        assertNotNull(params.get(\"timestamp\"));\n    }\n\n    @Test\n    public void testRegisterUploaderStrategy() {\n        String className = \"myUploadStrategy\";\n        Cloudinary.registerUploaderStrategy(className);\n        assertEquals(className, Cloudinary.UPLOAD_STRATEGIES.get(0));\n    }\n\n    @Test\n    public void testRegisterApiStrategy() {\n        String className = \"myApiStrategy\";\n        Cloudinary.registerAPIStrategy(className);\n        assertEquals(className, Cloudinary.API_STRATEGIES.get(0));\n    }\n\n    private void assertFieldsEqual(Object a, Object b) throws IllegalAccessException {\n        assertEquals(\"Two objects must be the same class\", a.getClass(), b.getClass());\n        Field[] fields = a.getClass().getFields();\n        for (Field field : fields) {\n            assertEquals(\"Field \" + field.getName() + \" should have equal values\", field.get(a), field.get(b));\n        }\n    }\n\n    private void randomizeFields(Object instance) throws IllegalAccessException {\n        Random rand = new Random();\n        Field[] fields = instance.getClass().getDeclaredFields();\n        for (Field field : fields) {\n            setRandomValue(rand, field, instance);\n        }\n    }\n\n    private void setRandomValue(Random rand, Field field, Object instance) throws IllegalAccessException {\n        field.setAccessible(true);\n        Type fieldType = field.getGenericType();\n        if (Modifier.isFinal(field.getModifiers()) || Modifier.isStatic(field.getModifiers())) {\n            return;\n        }\n\n        if (fieldType.equals(boolean.class) || fieldType.equals(Boolean.class)) {\n            field.set(instance, rand.nextBoolean());\n        } else if (fieldType.equals(int.class) || fieldType.equals(Integer.class)) {\n            field.set(instance, rand.nextInt());\n        } else if (fieldType.equals(long.class) || fieldType.equals(Long.class)) {\n            field.set(instance, rand.nextLong());\n        } else if (field.get(instance) instanceof List) {\n            field.set(instance, Collections.singletonList(cloudinary.randomPublicId()));\n        } else if (fieldType.equals(String.class)) {\n            field.set(instance, cloudinary.randomPublicId());\n        } else if (fieldType.equals(AuthToken.class)) {\n            AuthToken authToken = new AuthToken();\n            randomizeFields(authToken);\n            field.set(instance, authToken);\n        } else if (field.get(instance) instanceof HashMap) {\n            Map<String, Object> map = new HashMap<String, Object>();\n            map.put(cloudinary.randomPublicId(), rand.nextInt());\n            field.set(instance, map);\n        } else if (fieldType instanceof Class && Enum.class.isAssignableFrom((Class) fieldType)) {\n            field.set(instance, randomEnum((Class<Enum>) fieldType, rand));\n        } else {\n            throw new IllegalArgumentException(\"Object have unexpected field type, randomizing not supported: \" + field.getName() + \", type: \" + field.getType().getSimpleName());\n        }\n    }\n\n    private <T extends Enum<?>> T randomEnum(Class<T> clazz, Random random) {\n        return clazz.getEnumConstants()[random.nextInt(clazz.getEnumConstants().length)];\n    }\n}\n"
  },
  {
    "path": "cloudinary-core/src/test/java/com/cloudinary/transformation/ExpressionTest.java",
    "content": "package com.cloudinary.transformation;\n\nimport org.junit.Test;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertNull;\n\npublic class ExpressionTest {\n\n    @Test\n    public void normalize_null_null() {\n        String result = Expression.normalize(null);\n        assertNull(result);\n    }\n\n    @Test\n    public void normalize_number_number() {\n        String result = Expression.normalize(10);\n        assertEquals(\"10\", result);\n    }\n\n    @Test\n    public void normalize_emptyString_emptyString() {\n        String result = Expression.normalize(\"\");\n        assertEquals(\"\", result);\n    }\n\n    @Test\n    public void normalize_singleSpace_underscore() {\n        String result = Expression.normalize(\" \");\n        assertEquals(\"_\", result);\n    }\n\n    @Test\n    public void normalize_blankString_underscore() {\n        String result = Expression.normalize(\"   \");\n        assertEquals(\"_\", result);\n    }\n\n    @Test\n    public void normalize_underscore_underscore() {\n        String result = Expression.normalize(\"_\");\n        assertEquals(\"_\", result);\n    }\n\n    @Test\n    public void normalize_underscores_underscore() {\n        String result = Expression.normalize(\"___\");\n        assertEquals(\"_\", result);\n    }\n\n    @Test\n    public void normalize_underscoresAndSpaces_underscore() {\n        String result = Expression.normalize(\" _ __  _\");\n        assertEquals(\"_\", result);\n    }\n\n    @Test\n    public void normalize_arbitraryText_isNotAffected() {\n        String result = Expression.normalize(\"foobar\");\n        assertEquals(\"foobar\", result);\n    }\n\n    @Test\n    public void normalize_doubleAmpersand_replacedWithAndOperator() {\n        String result = Expression.normalize(\"foo && bar\");\n        assertEquals(\"foo_and_bar\", result);\n    }\n\n    @Test\n    public void normalize_doubleAmpersandWithNoSpaceAtEnd_isNotAffected() {\n        String result = Expression.normalize(\"foo&&bar\");\n        assertEquals(\"foo&&bar\", result);\n    }\n\n    @Test\n    public void normalize_width_recognizedAsVariableAndReplacedWithW() {\n        String result = Expression.normalize(\"width\");\n        assertEquals(\"w\", result);\n    }\n\n    @Test\n    public void normalize_initialAspectRatio_recognizedAsVariableAndReplacedWithIar() {\n        String result = Expression.normalize(\"initial_aspect_ratio\");\n        assertEquals(\"iar\", result);\n    }\n\n    @Test\n    public void normalize_dollarWidth_recognizedAsUserVariableAndNotAffected() {\n        String result = Expression.normalize(\"$width\");\n        assertEquals(\"$width\", result);\n    }\n\n    @Test\n    public void normalize_dollarInitialAspectRatio_recognizedAsUserVariableAndAsVariableReplacedWithAr() {\n        String result = Expression.normalize(\"$initial_aspect_ratio\");\n        assertEquals(\"$initial_ar\", result);\n    }\n\n    @Test\n    public void normalize_dollarMyWidth_recognizedAsUserVariableAndNotAffected() {\n        String result = Expression.normalize(\"$mywidth\");\n        assertEquals(\"$mywidth\", result);\n    }\n\n    @Test\n    public void normalize_dollarWidthWidth_recognizedAsUserVariableAndNotAffected() {\n        String result = Expression.normalize(\"$widthwidth\");\n        assertEquals(\"$widthwidth\", result);\n    }\n\n    @Test\n    public void normalize_dollarUnderscoreWidth_recognizedAsUserVariableAndNotAffected() {\n        String result = Expression.normalize(\"$_width\");\n        assertEquals(\"$_width\", result);\n    }\n\n    @Test\n    public void normalize_dollarUnderscoreX2Width_recognizedAsUserVariableAndNotAffected() {\n        String result = Expression.normalize(\"$__width\");\n        assertEquals(\"$_width\", result);\n    }\n\n    @Test\n    public void normalize_dollarX2Width_recognizedAsUserVariableAndNotAffected() {\n        String result = Expression.normalize(\"$$width\");\n        assertEquals(\"$$width\", result);\n    }\n\n    @Test\n    public void normalize_doesntReplaceVariable_1() {\n        String actual = Expression.normalize(\"$height_100\");\n        assertEquals(\"$height_100\", actual);\n    }\n\n    @Test\n    public void normalize_doesntReplaceVariable_2() {\n        String actual = Expression.normalize(\"$heightt_100\");\n        assertEquals(\"$heightt_100\", actual);\n    }\n\n    @Test\n    public void normalize_doesntReplaceVariable_3() {\n        String actual = Expression.normalize(\"$$height_100\");\n        assertEquals(\"$$height_100\", actual);\n    }\n\n    @Test\n    public void normalize_doesntReplaceVariable_4() {\n        String actual = Expression.normalize(\"$heightmy_100\");\n        assertEquals(\"$heightmy_100\", actual);\n    }\n\n    @Test\n    public void normalize_doesntReplaceVariable_5() {\n        String actual = Expression.normalize(\"$myheight_100\");\n        assertEquals(\"$myheight_100\", actual);\n    }\n\n    @Test\n    public void normalize_doesntReplaceVariable_6() {\n        String actual = Expression.normalize(\"$heightheight_100\");\n        assertEquals(\"$heightheight_100\", actual);\n    }\n\n    @Test\n    public void normalize_doesntReplaceVariable_7() {\n        String actual = Expression.normalize(\"$theheight_100\");\n        assertEquals(\"$theheight_100\", actual);\n    }\n\n    @Test\n    public void normalize_doesntReplaceVariable_8() {\n        String actual = Expression.normalize(\"$__height_100\");\n        assertEquals(\"$_height_100\", actual);\n    }\n\n    @Test\n    public void normalize_duration() {\n        String actual = Expression.normalize(\"duration\");\n        assertEquals(\"du\", actual);\n    }\n\n    @Test\n    public void normalize_previewDuration() {\n        String actual = Expression.normalize(\"preview:duration_2\");\n        assertEquals(\"preview:duration_2\", actual);\n    }\n}\n"
  },
  {
    "path": "cloudinary-core/src/test/java/com/cloudinary/transformation/LayerTest.java",
    "content": "package com.cloudinary.transformation;\n\nimport com.cloudinary.Cloudinary;\nimport com.cloudinary.Transformation;\nimport org.junit.After;\nimport org.junit.Before;\nimport org.junit.Test;\n\nimport static org.junit.Assert.*;\n\n/**\n * Created by amir on 03/11/2015.\n */\npublic class LayerTest {\n    private static final String DEFAULT_ROOT_PATH = \"https://res.cloudinary.com/test123/\";\n    private static final String DEFAULT_UPLOAD_PATH = DEFAULT_ROOT_PATH + \"image/upload/\";\n    private static final String VIDEO_UPLOAD_PATH = DEFAULT_ROOT_PATH + \"video/upload/\";\n    private Cloudinary cloudinary;\n\n    @Before\n    public void setUp() {\n        this.cloudinary = new Cloudinary(\"cloudinary://a:b@test123?load_strategies=false&analytics=false\");\n    }\n\n    @After\n    public void tearDown() throws Exception {\n\n    }\n\n    @Test\n    public void testOverlay() {\n        // should support overlay\n        Transformation transformation = new Transformation().overlay(\"text:hello\");\n        String result = cloudinary.url().transformation(transformation).generate(\"test\");\n        assertEquals(DEFAULT_UPLOAD_PATH + \"l_text:hello/test\", result);\n        // should not pass width/height to html if overlay\n        transformation = new Transformation().overlay(\"text:hello\").width(100).height(100);\n        result = cloudinary.url().transformation(transformation).generate(\"test\");\n        assertNull(transformation.getHtmlHeight());\n        assertNull(transformation.getHtmlWidth());\n        assertEquals(DEFAULT_UPLOAD_PATH + \"h_100,l_text:hello,w_100/test\", result);\n\n        transformation = new Transformation().overlay(new TextLayer().text(\"goodbye\"));\n        result = cloudinary.url().transformation(transformation).generate(\"test\");\n        assertEquals(DEFAULT_UPLOAD_PATH + \"l_text:goodbye/test\", result);\n    }\n\n    @Test\n        public void testUnderlay() {\n        Transformation transformation = new Transformation().underlay(\"text:hello\");\n        String result = cloudinary.url().transformation(transformation).generate(\"test\");\n        assertEquals(DEFAULT_UPLOAD_PATH + \"u_text:hello/test\", result);\n        // should not pass width/height to html if underlay\n        transformation = new Transformation().underlay(\"text:hello\").width(100).height(100);\n        result = cloudinary.url().transformation(transformation).generate(\"test\");\n        assertNull(transformation.getHtmlHeight());\n        assertNull(transformation.getHtmlWidth());\n        assertEquals(DEFAULT_UPLOAD_PATH + \"h_100,u_text:hello,w_100/test\", result);\n    }\n\n    @Test\n    public void testPublicIdWithDoubleUnderscoresInOverlay() {\n        Transformation transformation = new Transformation().width(300).height(200).crop(\"fill\").overlay(\"my__lake\");\n        String result = cloudinary.url().transformation(transformation).generate(\"sample.jpg\");\n        assertEquals(DEFAULT_UPLOAD_PATH + \"c_fill,h_200,l_my__lake,w_300/sample.jpg\", result);\n    }\n\n    @Test\n    public void testLayerOptions() {\n        Object tests[] = {\n                new Layer().publicId(\"logo\"),\n                \"logo\",\n                new Layer().publicId(\"logo__111\"),\n                \"logo__111\",\n                new Layer().publicId(\"folder/logo\"),\n                \"folder:logo\",\n                new Layer().publicId(\"logo\").type(\"private\"),\n                \"private:logo\",\n                new Layer().publicId(\"logo\").format(\"png\"),\n                \"logo.png\",\n                new Layer().resourceType(\"video\").publicId(\"cat\"),\n                \"video:cat\",\n                new TextLayer().text(\"Hello/World\").fontFamily(\"Arial\").fontSize(18),\n                \"text:Arial_18:Hello%252FWorld\",\n                new TextLayer().text(\"Hello World, Nice to meet you?\").fontFamily(\"Arial\").fontSize(18),\n                \"text:Arial_18:Hello%20World%252C%20Nice%20to%20meet%20you%3F\",\n                new TextLayer().text(\"Hello World, Nice to meet you?\").fontFamily(\"Arial\").fontSize(18)\n                        .fontWeight(\"bold\").fontStyle(\"italic\").letterSpacing(\"4\"),\n                \"text:Arial_18_bold_italic_letter_spacing_4:Hello%20World%252C%20Nice%20to%20meet%20you%3F\",\n                new SubtitlesLayer().publicId(\"sample_sub_en.srt\"), \"subtitles:sample_sub_en.srt\",\n                new SubtitlesLayer().publicId(\"sample_sub_he.srt\").fontFamily(\"Arial\").fontSize(40),\n                \"subtitles:Arial_40:sample_sub_he.srt\"};\n\n        for (int i = 0; i < tests.length; i += 2) {\n            Object layer = tests[i];\n            String expected = (String) tests[i + 1];\n            assertEquals(expected, layer.toString());\n        }\n    }\n\n    @Test(expected = IllegalArgumentException.class)\n    public void testOverlayError1() {\n        // Must supply font_family for text in overlay\n        cloudinary.url().transformation(new Transformation().overlay(new TextLayer().fontStyle(\"italic\"))).generate(\"test\");\n    }\n\n    @Test(expected = IllegalArgumentException.class)\n    public void testOverlayError2() {\n        // Must supply public_id for for non-text underlay\n        cloudinary.url().transformation(new Transformation().underlay(new Layer().resourceType(\"video\"))).generate(\"test\");\n    }\n\n    @Test\n    public void testResourceType() throws Exception {\n\n    }\n\n    @Test\n    public void testType() throws Exception {\n\n    }\n\n    @Test\n    public void testPublicId() throws Exception {\n\n    }\n\n    @Test\n    public void testFormat() throws Exception {\n\n    }\n\n    @Test\n    public void testToString() throws Exception {\n\n    }\n\n    @Test\n    public void testFormattedPublicId() throws Exception {\n\n    }\n}\n"
  },
  {
    "path": "cloudinary-http5/build.gradle",
    "content": "plugins {\n    id 'java-library'\n}\n\napply from: \"../java_shared.gradle\"\napply from: \"../publish.gradle\"\n\ntask ciTest( type: Test ) {\n    useJUnit {\n        excludeCategories 'com.cloudinary.test.TimeoutTest'\n        if (System.getProperty(\"CLOUDINARY_ACCOUNT_URL\") == \"\") {\n            exclude '**/AccountApiTest.class'\n        }\n    }\n}\n\ndependencies {\n    compile project(':cloudinary-core')\n    compile group: 'org.apache.commons', name: 'commons-lang3', version: '3.18.0'\n    api group: 'org.apache.httpcomponents.client5', name: 'httpclient5', version: '5.3.1'\n    api group: 'org.apache.httpcomponents.core5', name: 'httpcore5', version: '5.2.5'\n    testCompile project(':cloudinary-test-common')\n    testCompile group: 'org.hamcrest', name: 'java-hamcrest', version: '2.0.0.0'\n    testCompile group: 'pl.pragmatists', name: 'JUnitParams', version: '1.0.5'\n    testCompile group: 'junit', name: 'junit', version: '4.12'\n}\n\n// Publishing configuration moved to ../publish.gradle\n"
  },
  {
    "path": "cloudinary-http5/src/main/java/com/cloudinary/http5/ApiStrategy.java",
    "content": "package com.cloudinary.http5;\n\n\nimport com.cloudinary.Api;\nimport com.cloudinary.api.ApiResponse;\nimport com.cloudinary.api.exceptions.GeneralError;\nimport com.cloudinary.http5.api.Response;\nimport com.cloudinary.strategies.AbstractApiStrategy;\nimport com.cloudinary.utils.ObjectUtils;\nimport org.apache.hc.client5.http.classic.methods.*;\nimport org.apache.hc.client5.http.config.RequestConfig;\nimport org.apache.hc.client5.http.entity.UrlEncodedFormEntity;\nimport org.apache.hc.client5.http.impl.classic.CloseableHttpClient;\nimport org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;\nimport org.apache.hc.client5.http.impl.classic.HttpClientBuilder;\nimport org.apache.hc.client5.http.impl.classic.HttpClients;\nimport org.apache.hc.client5.http.io.HttpClientConnectionManager;\nimport org.apache.hc.core5.http.HttpEntity;\nimport org.apache.hc.core5.http.HttpHost;\nimport org.apache.hc.core5.http.NameValuePair;\nimport org.apache.hc.core5.http.io.entity.EntityUtils;\nimport org.apache.hc.core5.http.io.entity.StringEntity;\nimport org.apache.hc.core5.net.URIBuilder;\nimport org.apache.hc.core5.util.Timeout;\nimport org.cloudinary.json.JSONException;\nimport org.cloudinary.json.JSONObject;\n\nimport java.io.IOException;\nimport java.lang.reflect.Constructor;\nimport java.net.URISyntaxException;\nimport java.nio.charset.StandardCharsets;\nimport java.util.List;\nimport java.util.Map;\n\nimport static com.cloudinary.http5.ApiUtils.prepareParams;\nimport static com.cloudinary.http5.ApiUtils.setTimeouts;\n\npublic class ApiStrategy extends AbstractApiStrategy {\n\n    private static final String APACHE_HTTP_CLIENT_VERSION = System.getProperty(\"apache.http.client.version\", \"5.3.1\");\n\n    private CloseableHttpClient client;\n\n    public void init(Api api) {\n        super.init(api);\n\n        HttpClientBuilder clientBuilder = HttpClients.custom();\n        clientBuilder.useSystemProperties().setUserAgent(this.api.cloudinary.getUserAgent() + \" ApacheHttpClient/\" + APACHE_HTTP_CLIENT_VERSION);\n\n        HttpClientConnectionManager connectionManager = (HttpClientConnectionManager) api.cloudinary.config.properties.get(\"connectionManager\");\n        if (connectionManager != null) {\n            clientBuilder.setConnectionManager(connectionManager);\n        }\n\n        RequestConfig requestConfig = buildRequestConfig();\n\n        client = clientBuilder\n                .setDefaultRequestConfig(requestConfig)\n                .build();\n    }\n\n    public RequestConfig buildRequestConfig() {\n        RequestConfig.Builder requestConfigBuilder = RequestConfig.custom();\n\n        if (api.cloudinary.config.proxyHost != null && api.cloudinary.config.proxyPort != 0) {\n            HttpHost proxy = new HttpHost(api.cloudinary.config.proxyHost, api.cloudinary.config.proxyPort);\n            requestConfigBuilder.setProxy(proxy);\n        }\n\n        int timeout = this.api.cloudinary.config.timeout;\n        if (timeout > 0) {\n            requestConfigBuilder.setResponseTimeout(Timeout.ofSeconds(timeout))\n                    .setConnectionRequestTimeout(Timeout.ofSeconds(timeout))\n                    .setConnectTimeout(Timeout.ofSeconds(timeout));\n        }\n\n        return requestConfigBuilder.build();\n    }\n\n    @SuppressWarnings({\"rawtypes\", \"unchecked\"})\n    public ApiResponse callApi(Api.HttpMethod method, String apiUrl, Map<String, ?> params, Map options, String autorizationHeader) throws Exception {\n        HttpUriRequestBase request = prepareRequest(method, apiUrl, params, options);\n\n        request.setHeader(\"Authorization\", autorizationHeader);\n\n        return getApiResponse(request);\n    }\n\n    private ApiResponse getApiResponse(HttpUriRequestBase request) throws Exception {\n        String responseData = null;\n        int code = 0;\n        CloseableHttpResponse response;\n        try  {\n            response = client.execute(request);\n            code = response.getCode();\n            HttpEntity entity = response.getEntity();\n            if (entity != null) {\n                responseData = EntityUtils.toString(entity, StandardCharsets.UTF_8);\n            }\n        } catch (IOException e) {\n            throw new GeneralError(\"Error executing request: \" + e.getMessage());\n        }\n\n        if (code != 200) {\n            Map<String, Object> result;\n            try {\n                JSONObject responseJSON = new JSONObject(responseData);\n                result = ObjectUtils.toMap(responseJSON);\n            } catch (JSONException e) {\n                throw new RuntimeException(\"Invalid JSON response from server \" + e.getMessage());\n            }\n\n            // Extract the error message from the result map\n            String message = (String) ((Map<String, Object>) result.get(\"error\")).get(\"message\");\n\n            // Get the appropriate exception class based on status code\n            Class<? extends Exception> exceptionClass = Api.CLOUDINARY_API_ERROR_CLASSES.get(code);\n            if (exceptionClass != null) {\n                Constructor<? extends Exception> exceptionConstructor = exceptionClass.getConstructor(String.class);\n                throw exceptionConstructor.newInstance(message);\n            } else {\n                throw new GeneralError(\"Server returned unexpected status code - \" + code + \" - \" + responseData);\n            }\n        }\n\n        Map<String, Object> result;\n        try {\n            JSONObject responseJSON = new JSONObject(responseData);\n            result = ObjectUtils.toMap(responseJSON);\n        } catch (JSONException e) {\n            throw new RuntimeException(\"Invalid JSON response from server \" + e.getMessage());\n        }\n\n        return new Response(response, result);\n    }\n\n    @Override\n    public ApiResponse callAccountApi(Api.HttpMethod method, String apiUrl, Map<String, ?> params, Map options, String authorizationHeader) throws Exception {\n        // Prepare the request\n        HttpUriRequestBase request = prepareRequest(method, apiUrl, params, options);\n\n        // Add authorization header\n\n        request.setHeader(\"Authorization\", authorizationHeader);\n\n        // Execute the request and return the response\n        return getApiResponse(request);\n    }\n\n    private HttpUriRequestBase prepareRequest(Api.HttpMethod method, String apiUrl, Map<String, ? extends Object> params, Map<String, ?> options) throws URISyntaxException {\n        HttpUriRequestBase request;\n\n        String contentType = ObjectUtils.asString(options.get(\"content_type\"), \"urlencoded\");\n\n        switch (method) {\n            case GET:\n                URIBuilder uriBuilder = new URIBuilder(apiUrl);\n                for (NameValuePair param : prepareParams(params)) {\n                    uriBuilder.addParameter(param.getName(), param.getValue());\n                }\n                request = new HttpGet(uriBuilder.toString());\n                break;\n            case POST:\n                request = new HttpPost(apiUrl);\n                setEntity((HttpUriRequestBase) request, params, contentType);\n                break;\n            case PUT:\n                request = new HttpPut(apiUrl);\n                setEntity((HttpUriRequestBase) request, params, contentType);\n                break;\n            case DELETE:\n                request = new HttpDelete(apiUrl);\n                setEntity((HttpUriRequestBase) request, params, contentType);\n                break;\n            default:\n                throw new IllegalArgumentException(\"Unknown HTTP method\");\n        }\n        setTimeouts(request, options);\n        return request;\n    }\n\n    private void setEntity(HttpUriRequestBase request, Map<String, ?> params, String contentType) {\n        if (\"json\".equals(contentType)) {\n            JSONObject json = ObjectUtils.toJSON(params);\n            StringEntity entity = new StringEntity(json.toString(), StandardCharsets.UTF_8);\n            request.setEntity(entity);\n            request.setHeader(\"Content-Type\", \"application/json\");\n        } else {\n            List<NameValuePair> formParams = prepareParams(params);\n            request.setEntity(new UrlEncodedFormEntity(formParams, StandardCharsets.UTF_8));\n        }\n    }\n}\n"
  },
  {
    "path": "cloudinary-http5/src/main/java/com/cloudinary/http5/ApiUtils.java",
    "content": "package com.cloudinary.http5;\n\nimport com.cloudinary.utils.ObjectUtils;\nimport org.apache.hc.client5.http.classic.methods.HttpUriRequestBase;\nimport org.apache.hc.client5.http.config.RequestConfig;\nimport org.apache.hc.core5.http.NameValuePair;\nimport org.apache.hc.core5.http.message.BasicNameValuePair;\nimport org.apache.hc.core5.util.Timeout;\nimport org.cloudinary.json.JSONObject;\n\nimport java.util.*;\n\npublic final class ApiUtils {\n    private ApiUtils() {}\n\n    public static void setTimeouts(HttpUriRequestBase request, Map<String, ? extends Object> options) {\n        RequestConfig config = request.getConfig();\n        final RequestConfig.Builder builder;\n\n        if (config != null) {\n            builder = RequestConfig.copy(config);\n        } else {\n            builder = RequestConfig.custom();\n        }\n\n        Integer timeout = (Integer) options.get(\"timeout\");\n        if (timeout != null) {\n            builder.setResponseTimeout(Timeout.ofSeconds(timeout));\n        }\n\n        Integer connectionRequestTimeout = (Integer) options.get(\"connection_request_timeout\");\n        if (connectionRequestTimeout != null) {\n            builder.setConnectionRequestTimeout(Timeout.ofSeconds(connectionRequestTimeout));\n        }\n\n        Integer connectTimeout = (Integer) options.get(\"connect_timeout\");\n        if (connectTimeout != null) {\n            builder.setConnectTimeout(Timeout.ofSeconds(connectTimeout));\n        }\n\n        request.setConfig(builder.build());\n    }\n\n\n    public static List<NameValuePair> prepareParams(Map<String, ?> params) {\n        List<NameValuePair> requestParams = new ArrayList<>();\n\n        for (Map.Entry<String, ?> param : params.entrySet()) {\n            String key = param.getKey();\n            Object value = param.getValue();\n\n            if (value instanceof Iterable) {\n                // If the value is an Iterable, handle each item individually\n                for (Object single : (Iterable<?>) value) {\n                    requestParams.add(new BasicNameValuePair(key + \"[]\", ObjectUtils.asString(single)));\n                }\n            } else if (value instanceof Map) {\n                // Convert Map to JSON string manually to avoid empty object issues\n                JSONObject jsonObject = new JSONObject();\n                for (Map.Entry<?, ?> entry : ((Map<?, ?>) value).entrySet()) {\n                    jsonObject.put(entry.getKey().toString(), entry.getValue());\n                }\n                requestParams.add(new BasicNameValuePair(key, jsonObject.toString()));\n            } else {\n                // Handle simple key-value pairs\n                requestParams.add(new BasicNameValuePair(key, ObjectUtils.asString(value)));\n            }\n        }\n\n        return requestParams;\n    }\n}\n"
  },
  {
    "path": "cloudinary-http5/src/main/java/com/cloudinary/http5/UploaderStrategy.java",
    "content": "package com.cloudinary.http5;\n\nimport com.cloudinary.ProgressCallback;\nimport com.cloudinary.Uploader;\nimport com.cloudinary.Util;\nimport com.cloudinary.strategies.AbstractUploaderStrategy;\nimport com.cloudinary.utils.ObjectUtils;\nimport com.cloudinary.utils.StringUtils;\nimport org.apache.hc.client5.http.classic.methods.HttpPost;\nimport org.apache.hc.client5.http.classic.methods.HttpUriRequestBase;\nimport org.apache.hc.client5.http.config.RequestConfig;\nimport org.apache.hc.client5.http.entity.mime.ByteArrayBody;\nimport org.apache.hc.client5.http.entity.mime.FileBody;\nimport org.apache.hc.client5.http.entity.mime.HttpMultipartMode;\nimport org.apache.hc.client5.http.entity.mime.MultipartEntityBuilder;\nimport org.apache.hc.client5.http.impl.classic.CloseableHttpClient;\nimport org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;\nimport org.apache.hc.client5.http.impl.classic.HttpClientBuilder;\nimport org.apache.hc.client5.http.impl.classic.HttpClients;\nimport org.apache.hc.client5.http.io.HttpClientConnectionManager;\nimport org.apache.hc.core5.http.ContentType;\nimport org.apache.hc.core5.http.HttpHost;\nimport org.apache.hc.core5.http.ParseException;\nimport org.apache.hc.core5.http.io.entity.EntityUtils;\nimport org.apache.hc.core5.util.Timeout;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.nio.charset.StandardCharsets;\nimport java.util.Collection;\nimport java.util.Map;\n\npublic class UploaderStrategy extends AbstractUploaderStrategy {\n\n    private static final String APACHE_HTTP_CLIENT_VERSION = System.getProperty(\"apache.http.client.version\", \"5.3.1\");\n\n    private CloseableHttpClient client;\n\n    @Override\n    public void init(Uploader uploader) {\n        super.init(uploader);\n\n        HttpClientBuilder clientBuilder = HttpClients.custom();\n        clientBuilder.useSystemProperties().setUserAgent(cloudinary().getUserAgent() + \" ApacheHttpClient/\" + APACHE_HTTP_CLIENT_VERSION);\n\n        HttpClientConnectionManager connectionManager = (HttpClientConnectionManager) cloudinary().config.properties.get(\"connectionManager\");\n        if (connectionManager != null) {\n            clientBuilder.setConnectionManager(connectionManager);\n        }\n\n        RequestConfig requestConfig = buildRequestConfig();\n\n        client = clientBuilder\n                .setDefaultRequestConfig(requestConfig)\n                .build();\n    }\n\n    public RequestConfig buildRequestConfig() {\n        RequestConfig.Builder requestConfigBuilder = RequestConfig.custom();\n\n        if (cloudinary().config.proxyHost != null && cloudinary().config.proxyPort != 0) {\n            HttpHost proxy = new HttpHost(cloudinary().config.proxyHost, cloudinary().config.proxyPort);\n            requestConfigBuilder.setProxy(proxy);\n        }\n\n        int timeout = cloudinary().config.timeout;\n        if (timeout > 0) {\n            requestConfigBuilder.setResponseTimeout(Timeout.ofSeconds(timeout))\n                    .setConnectionRequestTimeout(Timeout.ofSeconds(timeout))\n                    .setConnectTimeout(Timeout.ofSeconds(timeout));\n        }\n\n        return requestConfigBuilder.build();\n    }\n\n    @SuppressWarnings({\"rawtypes\", \"unchecked\"})\n    @Override\n    public Map callApi(String action, Map<String, Object> params, Map options, Object file, ProgressCallback progressCallback) throws IOException {\n        if (progressCallback != null) {\n            throw new IllegalArgumentException(\"Progress callback is not supported\");\n        }\n\n        // Initialize options if passed as null\n        if (options == null) {\n            options = ObjectUtils.emptyMap();\n        }\n\n        boolean returnError = ObjectUtils.asBoolean(options.get(\"return_error\"), false);\n\n        if (requiresSigning(action, options)) {\n            uploader.signRequestParams(params, options);\n        } else {\n            Util.clearEmpty(params);\n        }\n\n        String apiUrl = buildUploadUrl(action, options);\n\n        // Prepare the request\n        HttpUriRequestBase request = prepareRequest(apiUrl, params, options, file);\n\n        // Execute the request and handle the response\n        String responseData;\n        int code;\n\n        try (CloseableHttpResponse response = client.execute(request)) {\n            code = response.getCode();\n            responseData = EntityUtils.toString(response.getEntity());\n        } catch (ParseException e) {\n            throw new RuntimeException(e);\n        }\n\n        // Process and return the response\n        return processResponse(returnError, code, responseData);\n    }\n\n    private HttpUriRequestBase prepareRequest(String apiUrl, Map<String, Object> params, Map<String, ?> options, Object file) throws IOException {\n        HttpPost request = new HttpPost(apiUrl);\n\n        MultipartEntityBuilder multipartBuilder = MultipartEntityBuilder.create()\n                .setCharset(StandardCharsets.UTF_8).setMode(HttpMultipartMode.LEGACY);\n\n        // Add text parameters\n        for (Map.Entry<String, Object> param : params.entrySet()) {\n            if (param.getValue() instanceof Collection) {\n                for (Object value : (Collection<?>) param.getValue()) {\n                    multipartBuilder.addTextBody(param.getKey() + \"[]\", ObjectUtils.asString(value), ContentType.TEXT_PLAIN.withCharset(StandardCharsets.UTF_8));\n                }\n            } else {\n                String value = param.getValue().toString();\n                if (StringUtils.isNotBlank(value)) {\n                    multipartBuilder.addTextBody(param.getKey(), value, ContentType.TEXT_PLAIN.withCharset(StandardCharsets.UTF_8));\n                }\n            }\n        }\n\n        // Add file part\n        addFilePart(multipartBuilder, file, options);\n\n        request.setEntity(multipartBuilder.build());\n\n        // Add extra headers if provided\n        Map<String, String> extraHeaders = (Map<String, String>) options.get(\"extra_headers\");\n        if (extraHeaders != null) {\n            for (Map.Entry<String, String> header : extraHeaders.entrySet()) {\n                request.addHeader(header.getKey(), header.getValue());\n            }\n        }\n\n        return request;\n    }\n\n\n    private void addFilePart(MultipartEntityBuilder multipartBuilder, Object file, Map<String, ?> options) throws IOException {\n        String filename = (String) options.get(\"filename\");\n\n        if (file instanceof String && !StringUtils.isRemoteUrl((String) file)) {\n            File _file = new File((String) file);\n            if (!_file.isFile() || !_file.canRead()) {\n                throw new IOException(\"File not found or unreadable: \" + file);\n            }\n            file = _file;\n        }\n\n        if (file instanceof File) {\n            if (filename == null) {\n                filename = ((File) file).getName();\n            }\n            // Encode filename properly\n            filename = new String(filename.getBytes(StandardCharsets.UTF_8), StandardCharsets.UTF_8);\n\n            // Create FileBody with correct filename encoding\n            FileBody fileBody = new FileBody((File) file, ContentType.APPLICATION_OCTET_STREAM, filename);\n            multipartBuilder.addPart(\"file\", fileBody);\n        } else if (file instanceof String) {\n            multipartBuilder.addTextBody(\"file\", (String) file, ContentType.TEXT_PLAIN);\n        } else if (file instanceof byte[]) {\n            if (filename == null) {\n                filename = \"file\";\n            }\n            ByteArrayBody byteArrayBody = new ByteArrayBody((byte[]) file, ContentType.APPLICATION_OCTET_STREAM, filename);\n            multipartBuilder.addPart(\"file\", byteArrayBody);\n        } else if (file == null) {\n            // No file to add\n        } else {\n            throw new IOException(\"Unrecognized file parameter \" + file);\n        }\n    }\n}\n"
  },
  {
    "path": "cloudinary-http5/src/main/java/com/cloudinary/http5/api/Response.java",
    "content": "package com.cloudinary.http5.api;\n\nimport com.cloudinary.api.ApiResponse;\nimport com.cloudinary.api.RateLimit;\nimport org.apache.hc.core5.http.Header;\nimport org.apache.hc.core5.http.HttpResponse;\nimport java.text.ParseException;\n\nimport java.text.DateFormat;\nimport java.text.SimpleDateFormat;\nimport java.util.HashMap;\nimport java.util.Locale;\nimport java.util.Map;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\npublic class Response extends HashMap implements ApiResponse {\n    private static final long serialVersionUID = -5458609797599845837L;\n    private final HttpResponse response;\n\n    @SuppressWarnings(\"unchecked\")\n    public Response(HttpResponse response, Map<String, Object> result) {\n        super(result);\n        this.response = response;\n    }\n\n    public HttpResponse getRawHttpResponse() {\n        return this.response;\n    }\n\n    private static final Pattern RATE_LIMIT_REGEX = Pattern\n            .compile(\"X-FEATURE(\\\\w*)RATELIMIT(-LIMIT|-RESET|-REMAINING)\", Pattern.CASE_INSENSITIVE);\n    private static final String RFC1123_PATTERN = \"EEE, dd MMM yyyy HH:mm:ss z\";\n    private static final DateFormat RFC1123 = new SimpleDateFormat(RFC1123_PATTERN, Locale.ENGLISH);\n\n    public Map<String, RateLimit> rateLimits() throws ParseException {\n        Header[] headers = this.response.getHeaders();\n        Map<String, RateLimit> limits = new HashMap<>();\n        for (Header header : headers) {\n            Matcher m = RATE_LIMIT_REGEX.matcher(header.getName());\n            if (m.matches()) {\n                String limitName = \"Api\";\n                RateLimit limit = limits.getOrDefault(limitName, new RateLimit());\n                if (!m.group(1).isEmpty()) {\n                    limitName = m.group(1);\n                }\n                if (m.group(2).equalsIgnoreCase(\"-limit\")) {\n                    limit.setLimit(Long.parseLong(header.getValue()));\n                } else if (m.group(2).equalsIgnoreCase(\"-remaining\")) {\n                    limit.setRemaining(Long.parseLong(header.getValue()));\n                } else if (m.group(2).equalsIgnoreCase(\"-reset\")) {\n                    limit.setReset(RFC1123.parse(header.getValue()));\n                }\n                limits.put(limitName, limit);\n            }\n        }\n        return limits;\n    }\n\n    public RateLimit apiRateLimit() throws ParseException {\n        return rateLimits().get(\"Api\");\n    }\n}\n"
  },
  {
    "path": "cloudinary-http5/src/test/java/com/cloudinary/test/AccountApiTest.java",
    "content": "package com.cloudinary.test;\n\npublic class AccountApiTest extends AbstractAccountApiTest {\n}\n"
  },
  {
    "path": "cloudinary-http5/src/test/java/com/cloudinary/test/ApiTest.java",
    "content": "package com.cloudinary.test;\n\nimport com.cloudinary.Cloudinary;\nimport com.cloudinary.api.ApiResponse;\nimport com.cloudinary.http5.ApiStrategy;\nimport com.cloudinary.utils.ObjectUtils;\nimport org.apache.hc.client5.http.config.RequestConfig;\nimport org.apache.hc.core5.http.HttpHost;\nimport org.apache.hc.core5.util.Timeout;\nimport org.junit.Test;\nimport org.junit.experimental.categories.Category;\n\nimport java.util.Map;\nimport java.util.UUID;\n\nimport static com.cloudinary.utils.ObjectUtils.asMap;\n\n\npublic class ApiTest extends AbstractApiTest {\n\n    @Test\n    public void testBuildRequestConfig_withProxyAndTimeout() {\n        Cloudinary cloudinary = new Cloudinary(\"cloudinary://test:test@test.com\");\n        cloudinary.config.proxyHost = \"127.0.0.1\";\n        cloudinary.config.proxyPort = 8080;\n        cloudinary.config.timeout = 15;\n\n        RequestConfig requestConfig = ((ApiStrategy)cloudinary.api().getStrategy()).buildRequestConfig();\n\n        assert(requestConfig.getProxy() != null);\n        HttpHost proxy = requestConfig.getProxy();\n        assert(\"127.0.0.1\" == proxy.getHostName());\n        assert(8080 == proxy.getPort());\n\n        assert(15000 == requestConfig.getConnectionRequestTimeout().toMilliseconds());\n        assert(15000 == requestConfig.getResponseTimeout().toMilliseconds());\n    }\n\n    @Test\n    public void testBuildRequestConfig_withoutProxy() {\n        Cloudinary cloudinary = new Cloudinary(\"cloudinary://test:test@test.com\");\n        cloudinary.config.timeout = 10;\n\n        RequestConfig requestConfig = ((ApiStrategy)cloudinary.api().getStrategy()).buildRequestConfig();\n\n        assert(requestConfig.getProxy() == null);\n        assert(10000 == requestConfig.getConnectionRequestTimeout().toMilliseconds());\n        assert(10000 == requestConfig.getResponseTimeout().toMilliseconds());\n    }\n\n    @Category(TimeoutTest.class)\n    @Test(expected = Exception.class)\n    public void testConnectTimeoutParameter() throws Exception {\n        Map<String, Object> options = asMap(\n                \"max_results\", 500,\n                \"connect_timeout\", 0.2);\n\n        try {\n            System.out.println(\"Setting connect timeout to 100 ms\");\n            ApiResponse result = cloudinary.api().resources(options);\n            System.out.println(\"Request completed without timeout\");\n        } catch (Exception e) {\n            throw new Exception(\"Connection timeout\", e);\n        }\n    }\n\n    @Category(TimeoutTest.class)\n    @Test(expected = Exception.class)\n    public void testTimeoutParameter() throws Exception {\n        // Set a very short request timeout to trigger a timeout exception\n        Map<String, Object> options = asMap(\n                \"max_results\", 500,\n                \"timeout\", Timeout.ofMilliseconds(1000)); // Set the timeout to 1 second\n\n        try {\n            ApiResponse result = cloudinary.api().resources(options);\n        } catch (Exception e) {\n            // Convert IOException to SocketTimeoutException if appropriate\n            throw new Exception(\"Socket timeout\");\n        }\n    }\n\n    @Category(TimeoutTest.class)\n    @Test(expected = Exception.class)\n    public void testUploaderTimeoutParameter() throws Exception {\n        Cloudinary cloudinary = new Cloudinary(\"cloudinary://test:test@test.com\");\n        cloudinary.config.uploadPrefix = \"https://10.255.255.1\";\n        String publicId = UUID.randomUUID().toString();\n        // Set a very short request timeout to trigger a timeout exception\n        Map<String, Object> options = asMap(\n                \"max_results\", 500,\n                \"timeout\", Timeout.ofMilliseconds(10)); // Set the timeout to 1 second\n\n        try {\n           Map result = cloudinary.uploader().addContext(asMap(\"caption\", \"new caption\"), new String[]{publicId, \"no-such-id\"}, options);\n        } catch (Exception e) {\n            // Convert IOException to SocketTimeoutException if appropriate\n            throw new Exception(\"Socket timeout\");\n        }\n    }\n\n}"
  },
  {
    "path": "cloudinary-http5/src/test/java/com/cloudinary/test/ContextTest.java",
    "content": "package com.cloudinary.test;\n\npublic class ContextTest extends AbstractContextTest {\n\n}"
  },
  {
    "path": "cloudinary-http5/src/test/java/com/cloudinary/test/FoldersApiTest.java",
    "content": "package com.cloudinary.test;\n\npublic class FoldersApiTest extends AbstractFoldersApiTest {\n}\n"
  },
  {
    "path": "cloudinary-http5/src/test/java/com/cloudinary/test/SearchTest.java",
    "content": "package com.cloudinary.test;\n\npublic class SearchTest extends AbstractSearchTest {\n}\n"
  },
  {
    "path": "cloudinary-http5/src/test/java/com/cloudinary/test/StreamingProfilesApiTest.java",
    "content": "package com.cloudinary.test;\n\n/**\n * Created by amir on 25/10/2016.\n */\npublic class StreamingProfilesApiTest extends AbstractStreamingProfilesApiTest {\n}\n"
  },
  {
    "path": "cloudinary-http5/src/test/java/com/cloudinary/test/StructuredMetadataTest.java",
    "content": "package com.cloudinary.test;\n\npublic class StructuredMetadataTest extends AbstractStructuredMetadataTest {\n}"
  },
  {
    "path": "cloudinary-http5/src/test/java/com/cloudinary/test/UploaderTest.java",
    "content": "package com.cloudinary.test;\n\npublic class UploaderTest extends AbstractUploaderTest {\n\n}"
  },
  {
    "path": "cloudinary-taglib/build.gradle",
    "content": "plugins {\n    id 'java-library'\n}\n\napply from: \"../java_shared.gradle\"\napply from: \"../publish.gradle\"\n\ntask ciTest( type: Test )\n\ndependencies {\n    compile project(':cloudinary-core')\n    compile group: 'org.apache.commons', name: 'commons-lang3', version:'3.18.0'\n    testCompile group: 'org.hamcrest', name: 'java-hamcrest', version:'2.0.0.0'\n    testCompile group: 'pl.pragmatists', name: 'JUnitParams', version:'1.0.5'\n    testCompile group: 'junit', name: 'junit', version:'4.12'\n    compile(group: 'javax.servlet', name: 'jsp-api', version:'2.0') {\n       /* This dependency was originally in the Maven provided scope, but the project was not of type war.\n       This behavior is not yet supported by Gradle, so this dependency has been converted to a compile dependency.\n       Please review and delete this closure when resolved. */\n    }\n}\n\n// Publishing configuration moved to ../publish.gradle"
  },
  {
    "path": "cloudinary-taglib/src/main/java/com/cloudinary/Singleton.java",
    "content": "package com.cloudinary;\n\n/**\n * This class contains a singleton in a generic way. This class is used by the tags to\n * retrieve the Cloudinary configuration.\n * <p>\n * the containing framework is responsible for registering the cloudinary configuration with the\n * Singleton, and then removing it on shutdown. This allows the user to use Spring or any other\n * framework without imposing additional dependencies on the cloudinary project.\n *\n * @author jpollak\n */\npublic final class Singleton {\n    private Singleton() {}\n\n    private static Cloudinary cloudinary;\n\n    public static void registerCloudinary(Cloudinary cloudinary) {\n        Singleton.cloudinary = cloudinary;\n    }\n\n    public static void deregisterCloudinary() {\n        cloudinary = null;\n    }\n\n    private static class DefaultCloudinaryHolder {\n        public static final Cloudinary INSTANCE = new Cloudinary();\n    }\n\n    public static Cloudinary getCloudinary() {\n        if (cloudinary == null) {\n            return DefaultCloudinaryHolder.INSTANCE;\n        }\n        return cloudinary;\n    }\n}\n"
  },
  {
    "path": "cloudinary-taglib/src/main/java/com/cloudinary/SingletonManager.java",
    "content": "package com.cloudinary;\n\npublic class SingletonManager {\n\n    private Cloudinary cloudinary;\n\n    public void setCloudinary(Cloudinary cloudinary) {\n        this.cloudinary = cloudinary;\n    }\n\n    public void init() {\n        Singleton.registerCloudinary(cloudinary);\n    }\n\n    public void destroy() {\n        Singleton.deregisterCloudinary();\n    }\n}\n"
  },
  {
    "path": "cloudinary-taglib/src/main/java/com/cloudinary/taglib/CloudinaryImageTag.java",
    "content": "package com.cloudinary.taglib;\n\nimport java.io.IOException;\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport javax.servlet.jsp.JspException;\nimport javax.servlet.jsp.JspWriter;\n\nimport com.cloudinary.*;\n\n/**\n * Generates an image html tag.<br>\n * For example,<br>\n * {@code <cl:img source='test' height='101' width='100' crop=\"crop\" />}\n * <br>is equivalent to:<br>\n * <pre>{@code\n * Transformation transformation = new Transformation()\n *      .width(100)\n *      .height(101)\n *      .crop(\"crop\");\n * String result = cloudinary.url()\n *      .transformation(transformation)\n *      .imageTag(\"test\", Cloudinary.asMap(\"alt\", \"my image\"));\n * }</pre>\n * <br>\n * Both code segments above produce the following tag:<br>\n * {@code <img src='http://res.cloudinary.com/test123/image/upload/c_crop,h_101,w_100/test' alt='my image'\n * height='101' width='100'/> }\n * <br>\n * @author jpollak\n * \n */\npublic class CloudinaryImageTag extends CloudinaryUrl {\n\n    private String id = null;\n    private String extraClasses = null;\n    \n    protected Map<String, String> prepareAttributes() {\n    \tMap<String, String> attributes = new HashMap<String, String>();\n        if (id != null) {\n            attributes.put(\"id\", id);\n        }\n        if (extraClasses != null) {\n            attributes.put(\"class\", extraClasses);\n        }\n        return attributes;\n    }\n    \n    public void doTag() throws JspException, IOException {\n        JspWriter out = getJspContext().getOut();\n        Url url = this.prepareUrl();\n        out.println(url.imageTag(prepareAttributes()));\n    }\n\n    public void setId(String id) {\n        this.id = id;\n    }\n\n    public String getId() {\n        return id;\n    }\n\n    public String getExtraClasses() {\n        return extraClasses;\n    }\n\n    public void setExtraClasses(String extraClasses) {\n        this.extraClasses = extraClasses;\n    }\n}"
  },
  {
    "path": "cloudinary-taglib/src/main/java/com/cloudinary/taglib/CloudinaryJsConfigTag.java",
    "content": "package com.cloudinary.taglib;\n\nimport java.io.IOException;\n\nimport javax.servlet.jsp.JspException;\nimport javax.servlet.jsp.JspWriter;\nimport javax.servlet.jsp.tagext.SimpleTagSupport;\n\nimport com.cloudinary.Cloudinary;\nimport com.cloudinary.Singleton;\n\npublic class CloudinaryJsConfigTag extends SimpleTagSupport {\n    @SuppressWarnings(\"unused\")\n    public void doTag() throws JspException, IOException {\n        Cloudinary cloudinary = Singleton.getCloudinary();\n        if (cloudinary == null) {\n            throw new JspException(\"Cloudinary config could not be located\");\n        }\n        JspWriter out = getJspContext().getOut();\n        out.println(\"<script language='javascript' type='text/javascript'>$.cloudinary.config({\");\n        print(out, \"api_key\", cloudinary.config.apiKey);\n        print(out, \"cloud_name\", cloudinary.config.cloudName);\n        print(out, \"cdn_subdomain\", cloudinary.config.cdnSubdomain);\n        print(out, \"private_cdn\", cloudinary.config.privateCdn);\n        print(out, \"secure_distribution\", cloudinary.config.secureDistribution);\n\n        out.println(\"});</script>\");\n    }\n\n    private void print(JspWriter out, String key, Object value) throws IOException {\n        if (value instanceof Boolean) {\n            out.println(key + \": \" + ((Boolean) value ? \"true\" : \"false\") + \",\");\n        } else {\n            out.println(key + \": \\\"\" + value + \"\\\",\");\n        }\n    }\n}\n"
  },
  {
    "path": "cloudinary-taglib/src/main/java/com/cloudinary/taglib/CloudinaryJsIncludeTag.java",
    "content": "package com.cloudinary.taglib;\n\nimport com.cloudinary.Cloudinary;\nimport com.cloudinary.Singleton;\n\nimport javax.servlet.jsp.JspException;\nimport javax.servlet.jsp.JspWriter;\nimport javax.servlet.jsp.tagext.SimpleTagSupport;\nimport java.io.IOException;\n\npublic class CloudinaryJsIncludeTag  extends SimpleTagSupport {\n    private boolean full = false;\n    private String base = \"javascripts/cloudinary/\";\n\n    public void doTag() throws JspException, IOException {\n        Cloudinary cloudinary = Singleton.getCloudinary();\n        if (cloudinary == null) {\n            throw new JspException(\"Cloudinary config could not be located\");\n        }\n        JspWriter out = getJspContext().getOut();\n        String[] basicFiles = {\"jquery.ui.widget.js\", \"jquery.iframe-transport.js\", \"jquery.fileupload.js\", \"jquery.cloudinary.js\"};\n        for (String file : basicFiles) {\n            out.println(\"<script type='text/javascript' src='\" + base + file + \"'></script>\");\n        }\n        if (full) {\n            String[] fullFiles = {\"canvas-to-blob.min.js\", \"load-image.min.js\", \"jquery.fileupload-process.js\", \"uery.fileupload-image.js\", \"jquery.fileupload-validate.js\"};\n            for (String file : fullFiles) {\n                out.println(\"<script type='text/javascript' src='\" + base + file + \"'></script>\");\n            }\n        }\n\n    }\n\n    public boolean isFull() {\n        return full;\n    }\n\n    public void setFull(boolean full) {\n        this.full = full;\n    }\n\n    public String getBase() {\n        return base;\n    }\n\n    public void setBase(String base) {\n        this.base = base;\n    }\n\n}\n"
  },
  {
    "path": "cloudinary-taglib/src/main/java/com/cloudinary/taglib/CloudinaryTransformationTag.java",
    "content": "package com.cloudinary.taglib;\n\nimport com.cloudinary.Transformation;\n\nimport javax.servlet.jsp.JspException;\nimport javax.servlet.jsp.tagext.DynamicAttributes;\nimport javax.servlet.jsp.tagext.SimpleTagSupport;\nimport java.io.IOException;\nimport java.util.HashMap;\nimport java.util.Map;\n\npublic class CloudinaryTransformationTag extends SimpleTagSupport implements DynamicAttributes {\n    private Map<String,Object> tagAttrs = new HashMap<String,Object>();\n\n    public void doTag() throws JspException, IOException {\n        Transformation transformation = new Transformation().params(tagAttrs);\n        getJspContext().getOut().print(transformation.generate());\n    }\n\n    @Override\n    public void setDynamicAttribute(String uri, String name, Object value) throws JspException {\n        tagAttrs.put(name, value);\n    }\n}\n"
  },
  {
    "path": "cloudinary-taglib/src/main/java/com/cloudinary/taglib/CloudinaryUnsignedUploadTag.java",
    "content": "package com.cloudinary.taglib;\n\nimport java.util.Map;\n\nimport com.cloudinary.Uploader;\n\npublic class CloudinaryUnsignedUploadTag extends CloudinaryUploadTag {\n\tpublic CloudinaryUnsignedUploadTag() {\n\t\tsuper();\n\t\tthis.unsigned = true;\n\t}\n\t\n\t@SuppressWarnings({ \"unchecked\", \"rawtypes\" })\n\tprotected String uploadTag(Uploader uploader, Map options, Map htmlOptions) {\n\t\treturn uploader.unsignedImageUploadTag(fieldName, uploadPreset, options, htmlOptions);\n\t}\n}\n"
  },
  {
    "path": "cloudinary-taglib/src/main/java/com/cloudinary/taglib/CloudinaryUploadTag.java",
    "content": "package com.cloudinary.taglib;\n\nimport java.io.IOException;\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.jsp.JspException;\nimport javax.servlet.jsp.PageContext;\nimport javax.servlet.jsp.tagext.SimpleTagSupport;\n\nimport com.cloudinary.Cloudinary;\nimport com.cloudinary.Singleton;\nimport com.cloudinary.Transformation;\nimport com.cloudinary.Uploader;\n\npublic class CloudinaryUploadTag extends SimpleTagSupport {\n\n    // HTML basics\n    private String id = null;\n    private String name = \"file\";\n    private String extraClasses = null;\n    private Boolean multiple = false;\n    \n    // Cloudinary Specific\n    private String tags = null;\n    protected String fieldName;\n    private String resourceType = \"auto\";\n    private String transformation = null;\n    private String eager = null;\n    private String callback = null;\n    private String publicId = null;\n    private String format = null;\n    private String notificationUrl = null;\n    private String eagerNotificationUrl = null;\n    private String proxy = null;\n    private String folder = null;\n    private String faceCoordinates = null;\n    private String customCoordinates = null;\n\tprivate String allowedFormats = null;\n    private String context = null;\n    private String ocr = null;\n    private String detection = null;\n    private String categorization = null;\n    private String similaritySearch = null;\n    private String backgroundRemoval = null;\n    private Float autoTagging = null;\n    protected String uploadPreset = null;\n\tprivate Boolean backup = null;\n    private Boolean exif = null;\n    private Boolean faces = null;\n    private Boolean colors = null;\n    private Boolean imageMetadata = null;\n    private Boolean useFilename = null;\n    private Boolean uniqueFilename = null;\n    private Boolean eagerAsync = null;\n    private Boolean invalidate = null;\n    private Boolean overwrite = null;\n    private Boolean phash = null;\n    protected boolean unsigned = false;\n    private Boolean mediaMetadata = null;\n\n\tpublic void doTag() throws JspException, IOException {\n        Cloudinary cloudinary = Singleton.getCloudinary();\n        if (cloudinary == null) {\n            throw new JspException(\"Cloudinary config could not be located\");\n        }\n        Uploader uploader = (Uploader)cloudinary.uploader();\n        \n        Map<String, Object> htmlOptions = new HashMap<String, Object>();\n        htmlOptions.put(\"type\", \"file\");\n        htmlOptions.put(\"name\", name);\n        htmlOptions.put(\"multiple\", multiple);\n        htmlOptions.put(\"class\", extraClasses);\n        htmlOptions.put(\"id\", id);\n        \n        Map<String, Object> options = new HashMap<String, Object>();\n        options.put(\"resource_type\", resourceType);\n        options.put(\"transformation\", transformation);\n        options.put(\"eager\", buildEager());\n        options.put(\"tags\", tags);\n        options.put(\"callback\", callback);\n        options.put(\"public_id\", publicId);\n        options.put(\"format\", format);\n        options.put(\"notification_url\", notificationUrl);\n        options.put(\"eager_notification_url\", eagerNotificationUrl);\n        options.put(\"proxy\", proxy);\n        options.put(\"folder\", folder);\n        options.put(\"backup\", backup);\n        options.put(\"exif\", exif);\n        options.put(\"faces\", faces);\n        options.put(\"colors\", colors);\n        options.put(\"image_metadata\", imageMetadata);\n        options.put(\"media_metadata\", mediaMetadata);\n        options.put(\"use_filename\", useFilename);\n        options.put(\"unique_filename\", uniqueFilename);\n        options.put(\"eager_async\", eagerAsync);\n        options.put(\"invalidate\", invalidate);\n        options.put(\"face_coordinates\", faceCoordinates);\n        options.put(\"custom_coordinates\", customCoordinates);\n        options.put(\"allowed_formats\", allowedFormats);\n        options.put(\"context\", context);\n        options.put(\"overwrite\", overwrite);\n        options.put(\"phash\", phash);\n        options.put(\"ocr\", ocr);\n        options.put(\"detection\", detection);\n        options.put(\"categorization\", categorization);\n        options.put(\"similarity_search\", similaritySearch);\n        options.put(\"auto_tagging\", autoTagging);\n        options.put(\"background_removal\", backgroundRemoval);\n        options.put(\"upload_preset\", uploadPreset);\n        options.put(\"unsigned\", unsigned);\n\n        buildCallbackUrl(options);\n\n        getJspContext().getOut().println(uploadTag(uploader, options, htmlOptions));\n    }\n\t\n    public void setId(String id) {\n        this.id = id;\n    }\n    \n    public String getId() {\n        return id;\n    }\n    \n    public void setName(String name) {\n        this.name = name;\n    }\n    \n    public String getName() {\n        return name;\n    }\n    \n    public void setExtraClasses(String extraClasses) {\n        this.extraClasses = extraClasses;\n    }\n    \n    public String getExtraClasses() {\n        return extraClasses;\n    }\n    \n    public void setTags(String tags) {\n        this.tags = tags;\n    }\n    \n    public String getTags() {\n        return tags;\n    }\n    \n    public void setFieldName(String fieldName) {\n        this.fieldName = fieldName;\n    }\n    \n    public String getFieldName() {\n        return fieldName;\n    }\n    \n    public void setTransformation(String transformation) {\n        this.transformation = transformation.replaceAll(\"\\\\s+\",\"/\");\n    }\n    \n    public String getTransformation() {\n        return transformation;\n    }\n\n\n    public String getResourceType() {\n        return resourceType;\n    }\n\n    public void setResourceType(String resourceType) {\n        this.resourceType = resourceType;\n    }\n\n    public Boolean getMultiple() {\n        return multiple;\n    }\n\n    public void setMultiple(Boolean multiple) {\n        this.multiple = multiple;\n    }\n\n    public String getEager() {\n        return eager;\n    }\n\n    public void setEager(String eager) {\n        this.eager = eager.replaceAll(\"\\\\s+\",\"|\");;\n    }\n\n    public String getPublicId() {\n        return publicId;\n    }\n\n    public void setPublicId(String publicId) {\n        this.publicId = publicId;\n    }\n\n    public String getFormat() {\n        return format;\n    }\n\n    public void setFormat(String format) {\n        this.format = format;\n    }\n\n    public String getNotificationUrl() {\n        return notificationUrl;\n    }\n\n    public void setNotificationUrl(String notificationUrl) {\n        this.notificationUrl = notificationUrl;\n    }\n\n    public String getEagerNotificationUrl() {\n        return eagerNotificationUrl;\n    }\n\n    public void setEagerNotificationUrl(String eagerNotificationUrl) {\n        this.eagerNotificationUrl = eagerNotificationUrl;\n    }\n\n    public String getProxy() {\n        return proxy;\n    }\n\n    public void setProxy(String proxy) {\n        this.proxy = proxy;\n    }\n\n    public String getFolder() {\n        return folder;\n    }\n\n    public void setFolder(String folder) {\n        this.folder = folder;\n    }\n\n    public boolean isBackup() {\n        return backup;\n    }\n\n    public void setBackup(boolean backup) {\n        this.backup = backup;\n    }\n\n    public boolean isExif() {\n        return exif;\n    }\n\n    public void setExif(boolean exif) {\n        this.exif = exif;\n    }\n\n    public boolean isFaces() {\n        return faces;\n    }\n\n    public void setFaces(boolean faces) {\n        this.faces = faces;\n    }\n\n    public boolean isColors() {\n        return colors;\n    }\n\n    public void setColors(boolean colors) {\n        this.colors = colors;\n    }\n\n    public boolean isImageMetadata() {\n        return imageMetadata;\n    }\n\n    public void setImageMetadata(boolean imageMetadata) {\n        this.imageMetadata = imageMetadata;\n    }\n\n    public boolean isUseFilename() {\n        return useFilename;\n    }\n\n    public void setUseFilename(boolean useFilename) {\n        this.useFilename = useFilename;\n    }\n\n    public boolean isUniqueFilename() {\n        return uniqueFilename;\n    }\n\n    public void setUniqueFilename(boolean uniqueFilename) {\n        this.uniqueFilename = uniqueFilename;\n    }\n\n    public boolean isEagerAsync() {\n        return eagerAsync;\n    }\n\n    public void setEagerAsync(boolean eagerAsync) {\n        this.eagerAsync = eagerAsync;\n    }\n\n    public boolean isInvalidate() {\n        return invalidate;\n    }\n\n    public void setInvalidate(boolean invalidate) {\n        this.invalidate = invalidate;\n    }\n\n    public String getCallback() {\n        return callback;\n    }\n\n    public void setCallback(String callback) {\n        this.callback = callback;\n    }\n    \n    public String getFaceCoordinates() {\n\t\treturn faceCoordinates;\n\t}\n\n\tpublic void setFaceCoordinates(String faceCoordinates) {\n\t\tthis.faceCoordinates = faceCoordinates;\n\t}\n\n    public String getCustomCoordinates() {\n        return customCoordinates;\n    }\n\n    public void setCustomCoordinates(String customCoordinates) {\n        this.customCoordinates = customCoordinates;\n    }\n\n\tpublic String getAllowedFormats() {\n\t\treturn allowedFormats;\n\t}\n\n\tpublic void setAllowedFormats(String allowedFormats) {\n\t\tthis.allowedFormats = allowedFormats;\n\t}\n\n\tpublic String getContext() {\n\t\treturn context;\n\t}\n\n\tpublic void setContext(String context) {\n\t\tthis.context = context;\n\t}\n\t\n\tpublic boolean isOverwrite() {\n\t\treturn overwrite;\n\t}\n\n\tpublic void setOverwrite(boolean overwrite) {\n\t\tthis.overwrite = overwrite;\n\t}\n\t\n\tpublic boolean isPhash() {\n\t\treturn phash;\n\t}\n\n\tpublic void setPhash(boolean phash) {\n\t\tthis.phash = phash;\n\t}\n\t\n\tpublic String getOcr() {\n\t\treturn ocr;\n\t}\n\n\tpublic void setOcr(String ocr) {\n\t\tthis.ocr = ocr;\n\t}\n\n\tpublic String getDetection() {\n\t\treturn detection;\n\t}\n\n\tpublic void setDetection(String detection) {\n\t\tthis.detection = detection;\n\t}\n\n\tpublic String getCategorization() {\n\t\treturn categorization;\n\t}\n\n\tpublic void setCategorization(String categorization) {\n\t\tthis.categorization = categorization;\n\t}\n\n\tpublic String getSimilaritySearch() {\n\t\treturn similaritySearch;\n\t}\n\n\tpublic void setSimilaritySearch(String similaritySearch) {\n\t\tthis.similaritySearch = similaritySearch;\n\t}\n\n    public String getBackgroundRemoval() {\n        return backgroundRemoval;\n    }\n\n    public void setBackgroundRemoval(String backgroundRemoval) {\n        this.backgroundRemoval = backgroundRemoval;\n    }\n\n\tpublic Float getAutoTagging() {\n\t\treturn autoTagging;\n\t}\n\n\tpublic void setAutoTagging(String autoTagging) {\n\t\tthis.autoTagging = Float.parseFloat(autoTagging);\n\t}\n\t\n\tpublic String getUploadPreset() {\n\t\treturn uploadPreset;\n\t}\n\n\tpublic void setUploadPreset(String uploadPreset) {\n\t\tthis.uploadPreset = uploadPreset;\n\t}\n\t\n\t@SuppressWarnings({ \"unchecked\", \"rawtypes\" })\n\tprotected String uploadTag(Uploader uploader, Map options, Map htmlOptions) {\n\t\treturn uploader.imageUploadTag(fieldName, options, htmlOptions);\n\t}\n\n    @SuppressWarnings({ \"rawtypes\", \"unchecked\" })\n\tprivate void buildCallbackUrl(Map options) {\n        String callback = (String) options.get(\"callback\");\n        if (callback == null || callback.isEmpty()) callback = Singleton.getCloudinary().config.callback;\n        if (callback == null || callback.isEmpty()) callback = \"/cloudinary_cors.html\";\n        if (!callback.matches(\"^https?://\")) {\n            PageContext context = (PageContext) getJspContext();\n            HttpServletRequest request = (HttpServletRequest) context.getRequest();\n            String callbackUrl = request.getScheme() + \"://\" + request.getServerName();\n            if (request.getScheme().equals(\"https\") && request.getServerPort() != 443 ||\n                    request.getScheme().equals(\"http\") && request.getServerPort() != 80) {\n                callbackUrl += \":\" + request.getServerPort() + request.getContextPath();\n            }\n            callbackUrl += callback;\n            options.put(\"callback\", callbackUrl);\n        }\n    }\n\n    private List<Transformation> buildEager() {\n    \tif (eager == null) return null;\n        String[] raws = eager.split(\"\\\\|\");\n        List<Transformation> list = new ArrayList<Transformation>();\n        for (String raw : raws) {\n            list.add(new Transformation().rawTransformation(raw));\n        }\n        return list;\n    }\n    \n}\n"
  },
  {
    "path": "cloudinary-taglib/src/main/java/com/cloudinary/taglib/CloudinaryUrl.java",
    "content": "package com.cloudinary.taglib;\n\nimport java.io.IOException;\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport javax.servlet.ServletRequest;\nimport javax.servlet.jsp.JspException;\nimport javax.servlet.jsp.JspWriter;\nimport javax.servlet.jsp.PageContext;\nimport javax.servlet.jsp.tagext.DynamicAttributes;\nimport javax.servlet.jsp.tagext.SimpleTagSupport;\n\nimport com.cloudinary.*;\n\n/**\n * Generates a cloudinary resource url\n *\n * <br>For example,<br> {@code <cl:url source='test' height='101' width='100' crop=\"crop\" />}\n * will produce<br> {@code http://res.cloudinary.com/test123/image/upload/c_crop,h_101,w_100/test}\n *\n */\npublic class CloudinaryUrl extends SimpleTagSupport implements DynamicAttributes {\n\n    protected String src = null;\n    private StoredFile storedSrc = null;\n\n    private String type = null;\n    private String resourceType = null;\n    private String format = null;\n\n    private String transformation = null;\n\n    private Boolean secure = null;\n    private Boolean cdnSubdomain = null;\n    private Boolean signed = null;\n    private Boolean useRootPath = null;\n    private Boolean secureCdnSubdomain = null;\n    \n    private String namedTransformation = null;\n    private String urlSuffix = null;\n\n    /** stores the dynamic attributes */\n    protected Map<String,Object> tagAttrs = new HashMap<String,Object>();\n    \n    protected Url prepareUrl() throws JspException {\n    \tCloudinary cloudinary = Singleton.getCloudinary();\n        if (cloudinary == null) {\n            throw new JspException(\"Cloudinary config could not be located\");\n        }\n        \n    \tUrl url = cloudinary.url();\n        if (storedSrc != null) {\n            url.source(storedSrc);\n        } else {\n            url.source(src);\n        }\n\n        Transformation baseTransformation = new Transformation().params(tagAttrs);\n        if (namedTransformation != null) baseTransformation.named(namedTransformation);\n        url.transformation(baseTransformation.chain().rawTransformation(transformation));\n        if (format != null) url.format(format);\n        if (type != null) url.type(type);\n        if (resourceType != null) url.resourceType(resourceType);\n        if (secure != null) {\n            url.secure(secure.booleanValue());\n        } else if(Boolean.TRUE.equals(isSecureRequest())) {\n            url.secure(true);\n        }\n        if (cdnSubdomain != null) url.cdnSubdomain(cdnSubdomain.booleanValue());\n        if (signed != null) url.signed(signed.booleanValue());\n        if (useRootPath != null) url.useRootPath(useRootPath);\n        if (urlSuffix != null) url.suffix(urlSuffix);\n        if (secureCdnSubdomain != null) url.secureCdnSubdomain(secureCdnSubdomain);\n        return url;\n    }\n    \n    public void doTag() throws JspException, IOException {\n        JspWriter out = getJspContext().getOut();\n        Url url = this.prepareUrl();\n        out.println(url.generate());\n    }\n\n    public void setSrc(String src) {\n        this.src = src;\n    }\n\n    public StoredFile getStoredSrc() {\n        return storedSrc;\n    }\n\n    public void setStoredSrc(StoredFile storedSrc) {\n        this.storedSrc = storedSrc;\n    }\n\n    public String getSrc() {\n        return src;\n    }\n\n    public void setFormat(String format) {\n        this.format = format;\n    }\n\n    public String getFormat() {\n        return format;\n    }\n\n    public String getType() {\n        return type;\n    }\n\n    public void setType(String type) {\n        this.type = type;\n    }\n\n    public String getResourceType() {\n        return resourceType;\n    }\n\n    public void setResourceType(String resourceType) {\n        this.resourceType = resourceType;\n    }\n\n\n    public String getTransformation() {\n        return transformation;\n    }\n\n    public void setTransformation(String transformation) {\n        this.transformation = transformation.replaceAll(\"\\\\s\",\"/\");\n    }\n\n    public Boolean getSecure() {\n        return secure;\n    }\n\n    public void setSecure(Boolean secure) {\n        this.secure = secure;\n    }\n\n    public Boolean getCdnSubdomain() {\n        return cdnSubdomain;\n    }\n\n    public void setCdnSubdomain(Boolean cdnSubdomain) {\n        this.cdnSubdomain = cdnSubdomain;\n    }\n\n    public Boolean getSigned() {\n        return signed;\n    }\n\n    public void setSigned(Boolean signed) {\n        this.signed = signed;\n    }\n\n    public String getNamed() {\n        return namedTransformation;\n    }\n\n    public void setNamed(String namedTransformation) {\n        this.namedTransformation = namedTransformation;\n    }\n\n    @Override\n    public void setDynamicAttribute(String uri, String name, Object value) throws JspException {\n        tagAttrs.put(name, value);\n    }\n\n    private Boolean isSecureRequest() {\n        PageContext context = (PageContext) getJspContext();\n        if (context == null) return null;\n        ServletRequest request = context.getRequest();\n        return request.getScheme().equals(\"https\");\n    }\n\n\tpublic Boolean getUseRootPath() {\n\t\treturn useRootPath;\n\t}\n\n\tpublic void setUseRootPath(Boolean useRootPath) {\n\t\tthis.useRootPath = useRootPath;\n\t}\n\n\tpublic Boolean getSecureCdnSubdomain() {\n\t\treturn secureCdnSubdomain;\n\t}\n\n\tpublic void setSecureCdnSubdomain(Boolean secureCdnSubdomain) {\n\t\tthis.secureCdnSubdomain = secureCdnSubdomain;\n\t}\n\n\tpublic String getUrlSuffix() {\n\t\treturn urlSuffix;\n\t}\n\n\tpublic void setUrlSuffix(String urlSuffix) {\n\t\tthis.urlSuffix = urlSuffix;\n\t}\n}\n"
  },
  {
    "path": "cloudinary-taglib/src/main/java/com/cloudinary/taglib/CloudinaryVideoTag.java",
    "content": "package com.cloudinary.taglib;\n\nimport java.io.IOException;\nimport java.util.Map;\n\nimport javax.servlet.jsp.JspException;\nimport javax.servlet.jsp.JspWriter;\n\nimport com.cloudinary.Transformation;\nimport com.cloudinary.Url;\n\npublic class CloudinaryVideoTag extends CloudinaryImageTag {\n\tprivate String sourceTypes;\n\tprivate Object poster;\n\tprivate Boolean autoplay;\n\tprivate Boolean controls;\n\tprivate Boolean loop;\n\tprivate Boolean muted;\n\tprivate Boolean preload;\n\t\n\tpublic Boolean getAutoplay() {\n\t\treturn autoplay;\n\t}\n\tpublic void setAutoplay(Boolean autoplay) {\n\t\tthis.autoplay = autoplay;\n\t}\n\tpublic Boolean getControls() {\n\t\treturn controls;\n\t}\n\tpublic void setControls(Boolean controls) {\n\t\tthis.controls = controls;\n\t}\n\tpublic Boolean getLoop() {\n\t\treturn loop;\n\t}\n\tpublic void setLoop(Boolean loop) {\n\t\tthis.loop = loop;\n\t}\n\tpublic Boolean getMuted() {\n\t\treturn muted;\n\t}\n\tpublic void setMuted(Boolean muted) {\n\t\tthis.muted = muted;\n\t}\n\tpublic Boolean getPreload() {\n\t\treturn preload;\n\t}\n\tpublic void setPreload(Boolean preload) {\n\t\tthis.preload = preload;\n\t}\n\tpublic Object getPoster() {\n\t\treturn poster;\n\t}\n\tpublic void setPoster(Object poster) {\n\t\tthis.poster = poster;\n\t}\n\t\n\tpublic String getSourceTypes() {\n\t\treturn sourceTypes;\n\t}\n\tpublic void setSourceTypes(String sourceTypes) {\n\t\tthis.sourceTypes = sourceTypes;\n\t}\n\t\n\tpublic void doTag() throws JspException, IOException {\n        JspWriter out = getJspContext().getOut();\n        Url url = this.prepareUrl();\n        \n        String sourceTypes[] = null;\n        if (this.sourceTypes != null) {\n        \tsourceTypes = this.sourceTypes.split(\",\");\n        \turl.sourceTypes(sourceTypes);\n        }\n        \n        if (this.poster != null) {\n        \tif (this.poster.equals(\"false\")) {\n        \t\turl.poster(false);\n        \t} else {\n        \t\turl.poster(this.poster);\n        \t}\n        }\n        \n        Map<String, String> attributes = prepareAttributes();\n        \n        if (sourceTypes == null) sourceTypes = Url.DEFAULT_VIDEO_SOURCE_TYPES;\n        for (String sourceType : sourceTypes) {\n        \tString transformationAttribute = sourceType + \"Transformation\";\n        \tif (this.tagAttrs.containsKey(transformationAttribute)) {\n        \t\tTransformation transformation = null;\n        \t\tObject transformationAttrValue = tagAttrs.remove(transformationAttribute);\n        \t\tif (transformationAttrValue instanceof Transformation) {\n        \t\t\ttransformation = (Transformation) transformationAttrValue;\n        \t\t} else if (transformationAttrValue instanceof Map) {\n        \t\t\ttransformation = new Transformation().params((Map) transformationAttrValue);\n        \t\t} else {\n        \t\t\ttransformation = new Transformation().rawTransformation((String) transformationAttrValue);\n        \t\t}\n        \t\turl.sourceTransformationFor(sourceType, transformation );\n        \t}\n        }\n        \n        if (autoplay != null) attributes.put(\"autoplay\", autoplay.toString());\n        if (controls != null) attributes.put(\"controls\", controls.toString());\n        if (loop != null) attributes.put(\"loop\", loop.toString());\n        if (muted != null) attributes.put(\"muted\", muted.toString());\n        if (preload != null) attributes.put(\"preload\", preload.toString());\n        \n        out.println(url.videoTag(attributes));\n    }\n}\n"
  },
  {
    "path": "cloudinary-taglib/src/main/resources/META-INF/cloudinary.tld",
    "content": "<taglib xmlns=\"http://java.sun.com/xml/ns/j2ee\"\n        xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n        xsi:schemaLocation=\"http://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd\"\n        version=\"2.0\">\n    <tlib-version>1.1</tlib-version>\n    <jsp-version>2.0</jsp-version>\n    <short-name>Cloudinary Taglib</short-name>\n    <uri>http://cloudinary.com/jsp/taglib</uri>\n    <tag>\n        <name>upload</name>\n        <tag-class>com.cloudinary.taglib.CloudinaryUploadTag</tag-class>\n        <body-content>scriptless</body-content>\n        <attribute>\n            <name>fieldName</name>\n            <required>true</required>\n            <rtexprvalue>true</rtexprvalue>\n        </attribute>\n        <attribute>\n            <name>id</name>\n            <required>false</required>\n            <rtexprvalue>true</rtexprvalue>\n        </attribute>\n        <attribute>\n            <name>name</name>\n            <required>false</required>\n            <rtexprvalue>true</rtexprvalue>\n        </attribute>\n        <attribute>\n            <name>tags</name>\n            <required>false</required>\n            <rtexprvalue>true</rtexprvalue>\n        </attribute>\n        <attribute>\n            <name>extraClasses</name>\n            <required>false</required>\n            <rtexprvalue>true</rtexprvalue>\n        </attribute>\n        <attribute>\n            <name>resourceType</name>\n            <required>false</required>\n            <rtexprvalue>true</rtexprvalue>\n        </attribute>\n        <attribute>\n            <name>transformation</name>\n            <required>false</required>\n            <rtexprvalue>true</rtexprvalue>\n        </attribute>\n        <attribute>\n            <name>callback</name>\n            <required>false</required>\n            <rtexprvalue>true</rtexprvalue>\n        </attribute>\n        <attribute>\n            <name>eager</name>\n            <required>false</required>\n            <rtexprvalue>true</rtexprvalue>\n        </attribute>\n        <attribute>\n            <name>publicId</name>\n            <required>false</required>\n            <rtexprvalue>true</rtexprvalue>\n        </attribute>\n        <attribute>\n            <name>format</name>\n            <required>false</required>\n            <rtexprvalue>true</rtexprvalue>\n        </attribute>\n        <attribute>\n            <name>notificationUrl</name>\n            <required>false</required>\n            <rtexprvalue>true</rtexprvalue>\n        </attribute>\n        <attribute>\n            <name>eagerNotificationUrl</name>\n            <required>false</required>\n            <rtexprvalue>true</rtexprvalue>\n        </attribute>\n        <attribute>\n            <name>proxy</name>\n            <required>false</required>\n            <rtexprvalue>true</rtexprvalue>\n        </attribute>\n        <attribute>\n            <name>folder</name>\n            <required>false</required>\n            <rtexprvalue>true</rtexprvalue>\n        </attribute>\n        <attribute>\n            <name>backup</name>\n            <required>false</required>\n            <rtexprvalue>true</rtexprvalue>\n        </attribute>\n        <attribute>\n            <name>exif</name>\n            <required>false</required>\n            <rtexprvalue>true</rtexprvalue>\n        </attribute>\n        <attribute>\n            <name>colors</name>\n            <required>false</required>\n            <rtexprvalue>true</rtexprvalue>\n        </attribute>\n        <attribute>\n            <name>faces</name>\n            <required>false</required>\n            <rtexprvalue>true</rtexprvalue>\n        </attribute>\n        <attribute>\n            <name>imageMetadata</name>\n            <required>false</required>\n            <rtexprvalue>true</rtexprvalue>\n        </attribute>\n        <attribute>\n            <name>useFilename</name>\n            <required>false</required>\n            <rtexprvalue>true</rtexprvalue>\n        </attribute>\n        <attribute>\n            <name>uniqueFilename</name>\n            <required>false</required>\n            <rtexprvalue>true</rtexprvalue>\n        </attribute>\n        <attribute>\n            <name>eagerAsync</name>\n            <required>false</required>\n            <rtexprvalue>true</rtexprvalue>\n        </attribute>\n        <attribute>\n            <name>invalidate</name>\n            <required>false</required>\n            <rtexprvalue>true</rtexprvalue>\n        </attribute>\n        <attribute>\n            <name>multiple</name>\n            <required>false</required>\n            <rtexprvalue>true</rtexprvalue>\n        </attribute>\n        <attribute>\n            <name>faceCoordinates</name>\n            <required>false</required>\n            <rtexprvalue>true</rtexprvalue>\n        </attribute>\n        <attribute>\n            <name>customCoordinates</name>\n            <required>false</required>\n            <rtexprvalue>true</rtexprvalue>\n        </attribute>\n        <attribute>\n            <name>allowedFormats</name>\n            <required>false</required>\n            <rtexprvalue>true</rtexprvalue>\n        </attribute>\n        <attribute>\n            <name>context</name>\n            <required>false</required>\n            <rtexprvalue>true</rtexprvalue>\n        </attribute>\n        <attribute>\n            <name>overwrite</name>\n            <required>false</required>\n            <rtexprvalue>true</rtexprvalue>\n        </attribute>\n        <attribute>\n            <name>ocr</name>\n            <required>false</required>\n            <rtexprvalue>true</rtexprvalue>\n        </attribute>\n        <attribute>\n            <name>detection</name>\n            <required>false</required>\n            <rtexprvalue>true</rtexprvalue>\n        </attribute>\n        <attribute>\n            <name>categorization</name>\n            <required>false</required>\n            <rtexprvalue>true</rtexprvalue>\n        </attribute>\n        <attribute>\n            <name>similaritySearch</name>\n            <required>false</required>\n            <rtexprvalue>true</rtexprvalue>\n        </attribute>\n        <attribute>\n            <name>backgroundRemoval</name>\n            <required>false</required>\n            <rtexprvalue>true</rtexprvalue>\n        </attribute>\n        <attribute>\n            <name>autoTagging</name>\n            <required>false</required>\n            <rtexprvalue>true</rtexprvalue>\n        </attribute>\n        <attribute>\n            <name>phash</name>\n            <required>false</required>\n            <rtexprvalue>true</rtexprvalue>\n        </attribute>\n        <attribute>\n            <name>uploadPreset</name>\n            <required>false</required>\n            <rtexprvalue>true</rtexprvalue>\n        </attribute>\n    </tag>\n    <tag>\n        <name>unsignedUpload</name>\n        <tag-class>com.cloudinary.taglib.CloudinaryUnsignedUploadTag</tag-class>\n        <body-content>scriptless</body-content>\n        <attribute>\n            <name>fieldName</name>\n            <required>true</required>\n            <rtexprvalue>true</rtexprvalue>\n        </attribute>\n        <attribute>\n            <name>id</name>\n            <required>false</required>\n            <rtexprvalue>true</rtexprvalue>\n        </attribute>\n        <attribute>\n            <name>name</name>\n            <required>false</required>\n            <rtexprvalue>true</rtexprvalue>\n        </attribute>\n        <attribute>\n            <name>tags</name>\n            <required>false</required>\n            <rtexprvalue>true</rtexprvalue>\n        </attribute>\n        <attribute>\n            <name>extraClasses</name>\n            <required>false</required>\n            <rtexprvalue>true</rtexprvalue>\n        </attribute>\n        <attribute>\n            <name>resourceType</name>\n            <required>false</required>\n            <rtexprvalue>true</rtexprvalue>\n        </attribute>\n        <attribute>\n            <name>callback</name>\n            <required>false</required>\n            <rtexprvalue>true</rtexprvalue>\n        </attribute>\n        <attribute>\n            <name>publicId</name>\n            <required>false</required>\n            <rtexprvalue>true</rtexprvalue>\n        </attribute>\n        <attribute>\n            <name>folder</name>\n            <required>false</required>\n            <rtexprvalue>true</rtexprvalue>\n        </attribute>\n        <attribute>\n            <name>multiple</name>\n            <required>false</required>\n            <rtexprvalue>true</rtexprvalue>\n        </attribute>\n        <attribute>\n            <name>faceCoordinates</name>\n            <required>false</required>\n            <rtexprvalue>true</rtexprvalue>\n        </attribute>\n        <attribute>\n            <name>customCoordinates</name>\n            <required>false</required>\n            <rtexprvalue>true</rtexprvalue>\n        </attribute>\n        <attribute>\n            <name>context</name>\n            <required>false</required>\n            <rtexprvalue>true</rtexprvalue>\n        </attribute>\n        <attribute>\n            <name>uploadPreset</name>\n            <required>false</required>\n            <rtexprvalue>true</rtexprvalue>\n        </attribute>\n    </tag>\n    <tag>\n        <name>transformation</name>\n        <tag-class>com.cloudinary.taglib.CloudinaryTransformationTag</tag-class>\n        <body-content>empty</body-content>\n        <dynamic-attributes>true</dynamic-attributes>\n    </tag>\n    <tag>\n        <name>image</name>\n        <tag-class>com.cloudinary.taglib.CloudinaryImageTag</tag-class>\n        <body-content>scriptless</body-content>\n        <attribute>\n            <name>id</name>\n            <required>false</required>\n            <rtexprvalue>true</rtexprvalue>\n        </attribute>\n        <attribute>\n            <name>extraClasses</name>\n            <required>false</required>\n            <rtexprvalue>true</rtexprvalue>\n        </attribute>\n        <attribute>\n            <name>src</name>\n            <required>false</required> <!-- should be true but replaces publicId so can't break -->\n            <rtexprvalue>true</rtexprvalue>\n        </attribute>\n        <attribute>\n            <name>storedSrc</name>\n            <required>false</required> <!-- should be true but replaces publicId so can't break -->\n            <rtexprvalue>true</rtexprvalue>\n        </attribute>\n        <attribute>\n            <name>publicId</name><!-- Deprecated -->\n            <required>false</required>\n            <rtexprvalue>true</rtexprvalue>\n        </attribute>\n        <attribute>\n            <name>format</name>\n            <required>false</required>\n            <rtexprvalue>true</rtexprvalue>\n        </attribute>\n        <attribute>\n            <name>type</name>\n            <required>false</required>\n            <rtexprvalue>true</rtexprvalue>\n        </attribute>\n        <attribute>\n            <name>resourceType</name>\n            <required>false</required>\n            <rtexprvalue>true</rtexprvalue>\n        </attribute>\n        <attribute>\n            <name>transformation</name>\n            <required>false</required>\n            <rtexprvalue>true</rtexprvalue>\n        </attribute>\n        <attribute>\n            <name>secure</name>\n            <required>false</required>\n            <rtexprvalue>true</rtexprvalue>\n        </attribute>\n        <attribute>\n            <name>cdnSubdomain</name>\n            <required>false</required>\n            <rtexprvalue>true</rtexprvalue>\n        </attribute>\n        <attribute>\n            <name>signed</name>\n            <required>false</required>\n            <rtexprvalue>true</rtexprvalue>\n        </attribute>\n        <attribute>\n            <name>named</name>\n            <required>false</required>\n            <rtexprvalue>true</rtexprvalue>\n        </attribute>\n        <dynamic-attributes>true</dynamic-attributes>\n    </tag>\n    <tag>\n        <name>video</name>\n        <tag-class>com.cloudinary.taglib.CloudinaryVideoTag</tag-class>\n        <body-content>scriptless</body-content>\n        <attribute>\n            <name>id</name>\n            <required>false</required>\n            <rtexprvalue>true</rtexprvalue>\n        </attribute>\n        <attribute>\n            <name>extraClasses</name>\n            <required>false</required>\n            <rtexprvalue>true</rtexprvalue>\n        </attribute>\n        <attribute>\n            <name>src</name>\n            <required>false</required> <!-- should be true but replaces publicId so can't break -->\n            <rtexprvalue>true</rtexprvalue>\n        </attribute>\n        <attribute>\n            <name>storedSrc</name>\n            <required>false</required> <!-- should be true but replaces publicId so can't break -->\n            <rtexprvalue>true</rtexprvalue>\n        </attribute>\n        <attribute>\n            <name>publicId</name><!-- Deprecated -->\n            <required>false</required>\n            <rtexprvalue>true</rtexprvalue>\n        </attribute>\n        <attribute>\n            <name>format</name>\n            <required>false</required>\n            <rtexprvalue>true</rtexprvalue>\n        </attribute>\n        <attribute>\n            <name>type</name>\n            <required>false</required>\n            <rtexprvalue>true</rtexprvalue>\n        </attribute>\n        <attribute>\n            <name>resourceType</name>\n            <required>false</required>\n            <rtexprvalue>true</rtexprvalue>\n        </attribute>\n        <attribute>\n            <name>transformation</name>\n            <required>false</required>\n            <rtexprvalue>true</rtexprvalue>\n        </attribute>\n        <attribute>\n            <name>secure</name>\n            <required>false</required>\n            <rtexprvalue>true</rtexprvalue>\n        </attribute>\n        <attribute>\n            <name>cdnSubdomain</name>\n            <required>false</required>\n            <rtexprvalue>true</rtexprvalue>\n        </attribute>\n        <attribute>\n            <name>signed</name>\n            <required>false</required>\n            <rtexprvalue>true</rtexprvalue>\n        </attribute>\n        <attribute>\n            <name>named</name>\n            <required>false</required>\n            <rtexprvalue>true</rtexprvalue>\n        </attribute>\n        <attribute>\n            <name>sourceTypes</name>\n            <required>false</required>\n            <rtexprvalue>true</rtexprvalue>\n        </attribute>\n        <attribute>\n            <name>poster</name>\n            <required>false</required>\n            <rtexprvalue>true</rtexprvalue>\n        </attribute>\n        <attribute>\n            <name>autoplay</name>\n            <required>false</required>\n            <rtexprvalue>true</rtexprvalue>\n        </attribute>\n        <attribute>\n            <name>controls</name>\n            <required>false</required>\n            <rtexprvalue>true</rtexprvalue>\n        </attribute>\n        <attribute>\n            <name>loop</name>\n            <required>false</required>\n            <rtexprvalue>true</rtexprvalue>\n        </attribute>\n        <attribute>\n            <name>muted</name>\n            <required>false</required>\n            <rtexprvalue>true</rtexprvalue>\n        </attribute>\n        <attribute>\n            <name>preload</name>\n            <required>false</required>\n            <rtexprvalue>true</rtexprvalue>\n        </attribute>\n        <dynamic-attributes>true</dynamic-attributes>\n    </tag>\n    <tag>\n        <name>url</name>\n        <tag-class>com.cloudinary.taglib.CloudinaryUrl</tag-class>\n        <body-content>scriptless</body-content>\n        <attribute>\n            <name>src</name>\n            <required>false</required>\n            <rtexprvalue>true</rtexprvalue>\n        </attribute>\n        <attribute>\n            <name>storedSrc</name>\n            <required>false</required>\n            <rtexprvalue>true</rtexprvalue>\n        </attribute>\n        <attribute>\n            <name>format</name>\n            <required>false</required>\n            <rtexprvalue>true</rtexprvalue>\n        </attribute>\n        <attribute>\n            <name>type</name>\n            <required>false</required>\n            <rtexprvalue>true</rtexprvalue>\n        </attribute>\n        <attribute>\n            <name>resourceType</name>\n            <required>false</required>\n            <rtexprvalue>true</rtexprvalue>\n        </attribute>\n        <attribute>\n            <name>transformation</name>\n            <required>false</required>\n            <rtexprvalue>true</rtexprvalue>\n        </attribute>\n        <attribute>\n            <name>secure</name>\n            <required>false</required>\n            <rtexprvalue>true</rtexprvalue>\n        </attribute>\n        <attribute>\n            <name>cdnSubdomain</name>\n            <required>false</required>\n            <rtexprvalue>true</rtexprvalue>\n        </attribute>\n        <attribute>\n            <name>signed</name>\n            <required>false</required>\n            <rtexprvalue>true</rtexprvalue>\n        </attribute>\n        <attribute>\n            <name>named</name>\n            <required>false</required>\n            <rtexprvalue>true</rtexprvalue>\n        </attribute>\n        <attribute>\n            <name>urlSuffix</name>\n            <required>false</required>\n            <rtexprvalue>true</rtexprvalue>\n        </attribute>\n        <attribute>\n            <name>secureCdnSubdomain</name>\n            <required>false</required>\n            <rtexprvalue>true</rtexprvalue>\n        </attribute>\n        <attribute>\n            <name>useRootPath</name>\n            <required>false</required>\n            <rtexprvalue>true</rtexprvalue>\n        </attribute>\n        <dynamic-attributes>true</dynamic-attributes>\n    </tag>\n    <tag>\n        <name>jsconfig</name>\n        <tag-class>com.cloudinary.taglib.CloudinaryJsConfigTag</tag-class>\n        <body-content>empty</body-content>\n    </tag>\n    <tag>\n        <name>jsinclude</name>\n        <tag-class>com.cloudinary.taglib.CloudinaryJsIncludeTag</tag-class>\n        <body-content>empty</body-content>\n        <attribute>\n            <name>full</name>\n            <required>false</required>\n            <rtexprvalue>true</rtexprvalue>\n        </attribute>\n        <attribute>\n            <name>base</name>\n            <required>false</required>\n            <rtexprvalue>true</rtexprvalue>\n        </attribute>\n    </tag>\n</taglib>\n"
  },
  {
    "path": "cloudinary-test-common/build.gradle",
    "content": "plugins {\n    id 'java-library'\n}\n\napply from: \"../java_shared.gradle\"\napply from: \"../publish.gradle\"\n\ntask ciTest( type: Test )\n\ndependencies {\n    compile project(':cloudinary-core')\n    compile group: 'org.hamcrest', name: 'java-hamcrest', version: '2.0.0.0'\n    compile group: 'junit', name: 'junit', version: '4.12'\n    testCompile group: 'pl.pragmatists', name: 'JUnitParams', version: '1.0.5'\n}\n\n// Publishing configuration moved to ../publish.gradle"
  },
  {
    "path": "cloudinary-test-common/src/main/java/com/cloudinary/test/AbstractAccountApiTest.java",
    "content": "package com.cloudinary.test;\n\n\nimport com.cloudinary.Cloudinary;\nimport com.cloudinary.api.ApiResponse;\nimport com.cloudinary.provisioning.Account;\nimport com.cloudinary.utils.ObjectUtils;\nimport org.junit.*;\nimport org.junit.rules.ExpectedException;\nimport org.junit.rules.TestName;\n\nimport java.util.*;\n\nimport static java.util.Collections.emptyMap;\nimport static java.util.Collections.singletonMap;\nimport static junit.framework.TestCase.assertTrue;\nimport static org.junit.Assert.*;\n\npublic abstract class AbstractAccountApiTest extends MockableTest {\n    private static Random rand = new Random();\n    protected Account account;\n    private static Set<String> createdSubAccountIds = new HashSet<String>();\n    private static Set<String> createdUserIds = new HashSet<String>();\n    private static Set<String> createdGroupIds = new HashSet<String>();\n\n    @BeforeClass\n    public static void setUpClass() {\n\n    }\n\n    @Rule\n    public TestName currentTest = new TestName();\n    @Rule\n    public ExpectedException expectedException = ExpectedException.none();\n\n    @Before\n    public void setUp() throws Exception {\n        assumeCloudinaryAccountURLExist();\n        System.out.println(\"Running \" + this.getClass().getName() + \".\" + currentTest.getMethodName());\n        this.account = new Account(new Cloudinary());\n    }\n\n    @AfterClass\n    public static void tearDownClass() {\n        assumeCloudinaryAccountURLExist();\n        System.out.println(\"Start TearDownClass\");\n        Account account = new Account(new Cloudinary());\n        for (String createdSubAccountId : createdSubAccountIds) {\n            try {\n                account.deleteSubAccount(createdSubAccountId, null);\n            } catch (Exception e) {\n                e.printStackTrace();\n            }\n        }\n\n        for (String userId : createdUserIds) {\n            try {\n                account.deleteUser(userId, null);\n            } catch (Exception e) {\n                e.printStackTrace();\n            }\n        }\n\n        for (String groupId : createdGroupIds) {\n            try {\n                account.deleteUserGroup(groupId, null);\n            } catch (Exception e) {\n                e.printStackTrace();\n            }\n        }\n        System.out.println(\"### Deleted - SubAccounts:\"+createdSubAccountIds.size()+\", Users:\"+createdUserIds.size()+ \", UserGroups:\"+createdGroupIds.size());\n    }\n\n    @Test\n    public void testPassingCredentialsThroughOptions() throws Exception {\n        assumeCloudinaryAccountURLExist();\n        int exceptions = 0;\n\n        Map<String, Object> map = singletonMap(\"provisioning_api_secret\", new Object()) ;\n        try {\n            this.account.subAccounts(true, null, null, map);\n        } catch (IllegalArgumentException ignored){\n            exceptions++;\n        }\n\n        map = singletonMap(\"provisioning_api_key\", new Object()) ;\n        try {\n            this.account.subAccounts(true, null, null, map);\n        } catch (IllegalArgumentException ignored){\n            exceptions++;\n        }\n\n        map = new HashMap<String, Object>();\n        map.put(\"provisioning_api_key\", \"abc\");\n        map.put(\"provisioning_api_secret\", \"def\");\n\n        try {\n            this.account.subAccounts(true, null, null, map);\n        } catch (Exception ex){\n            assertTrue(ex.getMessage().contains(\"Invalid credentials\"));\n            exceptions++;\n        }\n\n        assertEquals(3, exceptions);\n    }\n\n    // Sub accounts tests\n    @Test\n    public void testGetSubAccount() throws Exception {\n        assumeCloudinaryAccountURLExist();\n        ApiResponse accountResponse = createSubAccount();\n        ApiResponse account = this.account.subAccount(accountResponse.get(\"id\").toString(), null);\n        assertNotNull(account);\n    }\n\n    @Test\n    public void testGetSubAccounts() throws Exception {\n        assumeCloudinaryAccountURLExist();\n        createSubAccount();\n        ApiResponse accounts = account.subAccounts(null, null, null, null);\n        assertNotNull(accounts);\n        assertTrue(((ArrayList) accounts.get(\"sub_accounts\")).size() >= 1);\n    }\n\n    @Test\n    public void testCreateSubAccount() throws Exception {\n        assumeCloudinaryAccountURLExist();\n        ApiResponse result = createSubAccount();\n        assertNotNull(result);\n\n        String message = \"\";\n        try {\n            // test that the parameters are passed correctly - throws exception since the from-account id doesn't exist:\n            account.createSubAccount(randomLetters(), null, emptyMap(), true, \"non-existing-id\", null);\n        } catch (Exception ex){\n            message = ex.getMessage();\n        }\n\n        assertTrue(message.contains(\"cannot find sub account\"));\n    }\n\n    @Test\n    public void testUpdateSubAccount() throws Exception {\n        assumeCloudinaryAccountURLExist();\n        ApiResponse subAccount = createSubAccount();\n        String newCloudName = randomLetters();\n        ApiResponse result = account.updateSubAccount(subAccount.get(\"id\").toString(), null, newCloudName, Collections.<String, String>emptyMap(), null, null);\n        assertNotNull(result);\n        assertEquals(result.get(\"cloud_name\"), newCloudName);\n    }\n\n    @Test\n    public void testDeleteSubAccount() throws Exception {\n        assumeCloudinaryAccountURLExist();\n        ApiResponse createResult = createSubAccount();\n        String id = createResult.get(\"id\").toString();\n        ApiResponse result = account.deleteSubAccount(id, null);\n        assertNotNull(result);\n        assertEquals(result.get(\"message\"), \"ok\");\n        createdSubAccountIds.remove(id);\n    }\n\n    // Users test\n    @Test\n    public void testGetUser() throws Exception {\n        assumeCloudinaryAccountURLExist();\n        ApiResponse user = createUser();\n        String userId = user.get(\"id\").toString();\n        ApiResponse result = account.user(userId, null);\n\n        assertNotNull(result);\n        deleteUser(userId);\n    }\n\n    @Test\n    public void testGetUsers() throws Exception {\n        assumeCloudinaryAccountURLExist();\n        String user1Id = createUser(Account.Role.MASTER_ADMIN).get(\"id\").toString();\n        String user2Id = createUser(Account.Role.MASTER_ADMIN).get(\"id\").toString();\n        ApiResponse result = account.users(null, Arrays.asList(user1Id, user2Id), null, null, null);\n        assertNotNull(result);\n        final ArrayList users = (ArrayList) result.get(\"users\");\n        ArrayList<String> returnedIds = new ArrayList<String>(2);\n\n        assertEquals(\"Should return two users\", 2, users.size());\n\n        returnedIds.add(((Map) users.get(0)).get(\"id\").toString());\n        returnedIds.add(((Map) users.get(1)).get(\"id\").toString());\n\n        assertTrue(\"User1 id should be in the result set\", returnedIds.contains(user1Id));\n        assertTrue(\"User2 id should be in the result set\", returnedIds.contains(user2Id));\n        deleteUser(user1Id);\n        deleteUser(user2Id);\n    }\n\n    @Test\n    public void testGetPendingUsers() throws Exception {\n        assumeCloudinaryAccountURLExist();\n        String id = createUser(Account.Role.BILLING).get(\"id\").toString();\n\n        ApiResponse pending = account.users(true, Collections.singletonList(id), null, null, null);\n        assertEquals(1, ((ArrayList) pending.get(\"users\")).size());\n\n        ApiResponse notPending = account.users(false, Collections.singletonList(id), null, null, null);\n        assertEquals(0, ((ArrayList) notPending.get(\"users\")).size());\n\n        ApiResponse all = account.users(null, Collections.singletonList(id), null, null, null);\n        assertEquals(1, ((ArrayList) all.get(\"users\")).size());\n    }\n\n    @Test\n    public void testGetUsersByPrefix() throws Exception {\n        assumeCloudinaryAccountURLExist();\n        final long timeMillis = System.currentTimeMillis();\n        final String userName = String.format(\"SDK TEST Get Users By Prefix %d\", timeMillis);\n        final String userEmail = String.format(\"sdk-test-get-users-by-prefix+%d@cloudinary.com\", timeMillis);\n\n        createUser(userName,\n                userEmail,\n                Account.Role.BILLING,\n                Collections.<String>emptyList());\n\n        ApiResponse userByPrefix = account.users(true, null, userName.substring(0, userName.length() - 1), null, null);\n        assertEquals(1, ((ArrayList) userByPrefix.get(\"users\")).size());\n\n        ApiResponse userByNonExistingPrefix = account.users(true, null, userName + \"zzz\", null, null);\n        assertEquals(0, ((ArrayList) userByNonExistingPrefix.get(\"users\")).size());\n    }\n\n    @Test\n    public void testGetUsersBySubAccountIds() throws Exception {\n        assumeCloudinaryAccountURLExist();\n        ApiResponse subAccount = createSubAccount();\n        final String subAccountId = subAccount.get(\"id\").toString();\n\n        final long timeMillis = System.currentTimeMillis();\n        final String userName = String.format(\"SDK TEST Get Users By Sub Account Ids %d\", timeMillis);\n        final String userEmail = String.format(\"sdk-test-get-users-by-sub-account-ids+%d@cloudinary.com\", timeMillis);\n\n        createUser(userName,\n                userEmail,\n                Account.Role.BILLING,\n                Collections.singletonList(subAccountId));\n\n        ApiResponse usersBySubAccount = account.users(true, null, userName, subAccountId, null);\n        assertEquals(1, ((ArrayList) usersBySubAccount.get(\"users\")).size());\n    }\n\n    @Test\n    public void testGetUsersThrowsWhenSubAccountIdDoesntExist() throws Exception {\n        assumeCloudinaryAccountURLExist();\n        final String subAccountId = randomLetters();\n        expectedException.expectMessage(\"Cannot find sub account with id \" + subAccountId);\n        account.users(true, null, null, subAccountId, null);\n    }\n\n    @Test\n    public void testCreateUser() throws Exception {\n        assumeCloudinaryAccountURLExist();\n        ApiResponse createResult = createSubAccount();\n        ApiResponse result = createUser(Collections.singletonList(createResult.get(\"id\").toString()));\n        assertNotNull(result);\n    }\n\n    @Test\n    public void testCreateUserWithOptions() throws Exception {\n        assumeCloudinaryAccountURLExist();\n        ApiResponse createResult = createSubAccount();\n        ApiResponse result = createUser(Collections.singletonList(createResult.get(\"id\").toString()), ObjectUtils.emptyMap());\n        assertNotNull(result);\n    }\n\n    @Test\n    public void testCreateUserEnabled() throws Exception {\n        assumeCloudinaryAccountURLExist();\n        ApiResponse createResult = createSubAccount();\n        ApiResponse result = createUser(Collections.singletonList(createResult.get(\"id\").toString()), true);\n        assertTrue((Boolean) result.get(\"enabled\"));\n    }\n\n    @Test\n    public void testCreateUserDisabled() throws Exception {\n        assumeCloudinaryAccountURLExist();\n        ApiResponse createResult = createSubAccount();\n        ApiResponse result = createUser(Collections.singletonList(createResult.get(\"id\").toString()), false);\n        assertFalse((Boolean) result.get(\"enabled\"));\n    }\n\n    @Test\n    public void testUpdateUser() throws Exception {\n        assumeCloudinaryAccountURLExist();\n        ApiResponse user = createUser(Account.Role.ADMIN);\n        String userId = user.get(\"id\").toString();\n        String newName = randomLetters();\n        ApiResponse result = account.updateUser(userId, newName, null, null, null, null);\n\n        assertNotNull(result);\n        assertEquals(result.get(\"name\"), newName);\n        deleteUser(userId);\n    }\n\n    @Test\n    public void testUpdateUserEnabled() throws Exception {\n        assumeCloudinaryAccountURLExist();\n        ApiResponse user = createUser(Account.Role.ADMIN);\n        String userId = user.get(\"id\").toString();\n        String newName = randomLetters();\n        ApiResponse result = account.updateUser(userId, newName, null, null, true, null, null);\n\n        assertNotNull(result);\n        assertTrue((Boolean) result.get(\"enabled\"));\n        deleteUser(userId);\n    }\n\n    @Test\n    public void testUpdateUserDisabled() throws Exception {\n        assumeCloudinaryAccountURLExist();\n        ApiResponse user = createUser(Account.Role.ADMIN);\n        String userId = user.get(\"id\").toString();\n        String newName = randomLetters();\n        ApiResponse result = account.updateUser(userId, newName, null, null, false, null, null);\n\n        assertNotNull(result);\n        assertFalse((Boolean) result.get(\"enabled\"));\n        deleteUser(userId);\n    }\n\n    @Test\n    public void testDeleteUser() throws Exception {\n        assumeCloudinaryAccountURLExist();\n        ApiResponse user = createUser(Collections.<String>emptyList());\n        String id = user.get(\"id\").toString();\n        ApiResponse result = account.deleteUser(id, null);\n        assertEquals(result.get(\"message\"), \"ok\");\n        createdUserIds.remove(id);\n    }\n\n    // groups\n    @Test\n    public void testCreateUserGroup() throws Exception {\n        assumeCloudinaryAccountURLExist();\n        ApiResponse group = createGroup();\n        assertNotNull(group);\n    }\n\n    @Test\n    public void testUpdateUserGroup() throws Exception {\n        assumeCloudinaryAccountURLExist();\n        ApiResponse group = createGroup();\n        String newName = randomLetters();\n        ApiResponse result = account.updateUserGroup(group.get(\"id\").toString(), newName, null);\n        assertNotNull(result);\n    }\n\n    @Test\n    public void testDeleteUserGroup() throws Exception {\n        assumeCloudinaryAccountURLExist();\n        ApiResponse group = createGroup();\n        String id = group.get(\"id\").toString();\n        ApiResponse result = account.deleteUserGroup(id, null);\n        assertNotNull(result);\n        assertEquals(result.get(\"ok\"), true);\n        createdGroupIds.remove(id);\n    }\n\n    @Test\n    public void testAddUserToUserGroup() throws Exception {\n        assumeCloudinaryAccountURLExist();\n        ApiResponse user = createUser();\n        ApiResponse group = createGroup();\n        String userId = user.get(\"id\").toString();\n        ApiResponse result = account.addUserToGroup(group.get(\"id\").toString(), userId, null);\n        assertNotNull(result);\n        deleteUser(userId);\n    }\n\n    @Test\n    public void testRemoveUserFromUserGroup() throws Exception {\n        assumeCloudinaryAccountURLExist();\n        ApiResponse user = createUser(Account.Role.MEDIA_LIBRARY_ADMIN);\n        ApiResponse group = createGroup();\n        String groupId = group.get(\"id\").toString();\n        String userId = user.get(\"id\").toString();\n        account.addUserToGroup(groupId, userId, null);\n        ApiResponse result = account.removeUserFromGroup(groupId, userId, null);\n        assertNotNull(result);\n        deleteUser(userId);\n    }\n\n    @Test\n    public void testListUserGroups() throws Exception {\n        assumeCloudinaryAccountURLExist();\n        createGroup();\n        ApiResponse result = account.userGroups();\n        assertNotNull(result);\n        assertTrue(((List) result.get(\"user_groups\")).size() >= 1);\n    }\n\n    @Test\n    public void testListUserGroup() throws Exception {\n        assumeCloudinaryAccountURLExist();\n        ApiResponse group = createGroup();\n        ApiResponse result = account.userGroup(group.get(\"id\").toString(), null);\n        assertNotNull(result);\n    }\n\n    @Test\n    public void testListUsersInGroup() throws Exception {\n        assumeCloudinaryAccountURLExist();\n        ApiResponse user1 = createUser();\n        ApiResponse user2 = createUser();\n        ApiResponse group = createGroup();\n        String groupId = group.get(\"id\").toString();\n        String user1Id = user1.get(\"id\").toString();\n        String user2Id = user2.get(\"id\").toString();\n        account.addUserToGroup(groupId, user1Id, null);\n        account.addUserToGroup(groupId, user2Id, null);\n        ApiResponse result = account.userGroupUsers(groupId, null);\n        assertNotNull(result);\n        assertTrue(((List) result.get(\"users\")).size() >= 2);\n        deleteUser(user1Id);\n        deleteUser(user2Id);\n    }\n\n    @Test\n    public void testGetAccessKeys() throws Exception {\n        ApiResponse createResult = createSubAccount();\n        ApiResponse result = account.getAccessKeys((String) createResult.get(\"id\"), ObjectUtils.emptyMap());\n        assertNotNull(result);\n    }\n\n    @Test\n    public void testCreateNewAccessKey() throws Exception {\n        ApiResponse createResult = createSubAccount();\n        String name = randomLetters();\n        ApiResponse result = account.createAccessKey((String)createResult.get(\"id\"), name, true, ObjectUtils.emptyMap());\n        assertNotNull(result);\n        assertTrue((Boolean) result.get(\"enabled\"));\n    }\n\n    @Test\n    public void testUpdateAccessKey() throws Exception {\n        ApiResponse createResult = createSubAccount();\n        String name = randomLetters();\n        ApiResponse result = account.createAccessKey((String)createResult.get(\"id\"), name, false, ObjectUtils.emptyMap());\n        assertNotNull(result);\n\n        String updatedName = randomLetters();\n        result = account.updateAccessKey((String)createResult.get(\"id\"), (String) result.get(\"api_key\"), updatedName, true, ObjectUtils.emptyMap());\n        assertNotNull(result);\n        assertEquals(updatedName, result.get(\"name\"));\n        assertTrue((Boolean) result.get(\"enabled\"));\n    }\n\n    @Test\n    public void testDeleteAccessKey() throws Exception {\n        ApiResponse createResult = createSubAccount();\n        String name = randomLetters();\n        ApiResponse result = account.createAccessKey((String)createResult.get(\"id\"), name, false, ObjectUtils.emptyMap());\n        assertNotNull(result);\n\n        result = account.deleteAccessKey((String)createResult.get(\"id\"), (String) result.get(\"api_key\"), ObjectUtils.emptyMap());\n        assertNotNull(result);\n    }\n\n\n    // Helpers\n    private ApiResponse createGroup() throws Exception {\n        assumeCloudinaryAccountURLExist();\n        String name = randomLetters();\n        ApiResponse userGroup = account.createUserGroup(name);\n        createdGroupIds.add(userGroup.get(\"id\").toString());\n        return userGroup;\n    }\n\n    private ApiResponse createUser() throws Exception {\n        assumeCloudinaryAccountURLExist();\n        return createUser(Collections.<String>emptyList());\n    }\n\n    private ApiResponse createUser(Account.Role role) throws Exception {\n        assumeCloudinaryAccountURLExist();\n        return createUser(Collections.<String>emptyList(), role);\n    }\n\n    private ApiResponse createUser(List<String> subAccountsIds) throws Exception {\n        assumeCloudinaryAccountURLExist();\n        return createUser(subAccountsIds, Account.Role.BILLING);\n    }\n\n    private ApiResponse createUser(List<String> subAccountsIds, Map<String, Object> options) throws Exception {\n        assumeCloudinaryAccountURLExist();\n        return createUser(subAccountsIds, Account.Role.BILLING, options);\n    }\n\n    private ApiResponse createUser(List<String> subAccountsIds, Boolean enabled) throws Exception {\n        assumeCloudinaryAccountURLExist();\n        return createUser(subAccountsIds, Account.Role.BILLING, enabled);\n    }\n\n    private ApiResponse createUser(List<String> subAccountsIds, Account.Role role) throws Exception {\n        assumeCloudinaryAccountURLExist();\n        String email = \"sdk+\" + SDK_TEST_TAG + randomLetters() + \"@cloudinary.com\";\n        return createUser(\"TestName\", email, role, subAccountsIds);\n    }\n\n    private ApiResponse createUser(List<String> subAccountsIds, Account.Role role, Map<String, Object> options) throws Exception {\n        assumeCloudinaryAccountURLExist();\n        String email = \"sdk+\" + SDK_TEST_TAG + randomLetters() + \"@cloudinary.com\";\n        ApiResponse user = account.createUser(\"TestUserJava\"+new Date().toString(), email, role, null, subAccountsIds, options);\n        createdUserIds.add(user.get(\"id\").toString());\n        return user;\n    }\n\n    private ApiResponse createUser(List<String> subAccountsIds, Account.Role role, Boolean enabled) throws Exception {\n        assumeCloudinaryAccountURLExist();\n        String email = \"sdk+\" + SDK_TEST_TAG + randomLetters() + \"@cloudinary.com\";\n        ApiResponse user = account.createUser(\"TestUserJava\"+new Date().toString(), email, role, enabled, subAccountsIds, null);\n        createdUserIds.add(user.get(\"id\").toString());\n        return user;\n    }\n\n    private ApiResponse createUser(final String name, String email, Account.Role role, List<String> subAccountsIds) throws Exception {\n        assumeCloudinaryAccountURLExist();\n        ApiResponse user = account.createUser(name, email, role, subAccountsIds, null);\n        createdUserIds.add(user.get(\"id\").toString());\n        return user;\n    }\n\n    private void deleteUser(String userId){\n        assumeCloudinaryAccountURLExist();\n        try {\n            account.deleteUser(userId, null);\n            createdUserIds.remove(userId);\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n    }\n\n\n    private ApiResponse createSubAccount() throws Exception {\n        assumeCloudinaryAccountURLExist();\n        ApiResponse subAccount = account.createSubAccount(randomLetters(), null, emptyMap(), true, null);\n        createdSubAccountIds.add(subAccount.get(\"id\").toString());\n        return subAccount;\n    }\n\n    private static String randomLetters() {\n        assumeCloudinaryAccountURLExist();\n        StringBuilder sb = new StringBuilder();\n        for (int i = 0; i < 10; i++) {\n            sb.append((char) ('a' + rand.nextInt('z' - 'a' + 1)));\n        }\n        return sb.toString();\n    }\n}\n"
  },
  {
    "path": "cloudinary-test-common/src/main/java/com/cloudinary/test/AbstractApiTest.java",
    "content": "package com.cloudinary.test;\n\nimport com.cloudinary.*;\nimport com.cloudinary.api.ApiResponse;\nimport com.cloudinary.api.exceptions.BadRequest;\nimport com.cloudinary.api.exceptions.NotFound;\nimport com.cloudinary.test.helpers.Feature;\nimport com.cloudinary.test.rules.RetryRule;\nimport com.cloudinary.transformation.TextLayer;\nimport com.cloudinary.utils.ObjectUtils;\nimport org.junit.*;\nimport org.junit.rules.TestName;\n\nimport java.io.IOException;\nimport java.text.SimpleDateFormat;\nimport java.util.*;\n\nimport static com.cloudinary.utils.ObjectUtils.asMap;\nimport static com.cloudinary.utils.ObjectUtils.emptyMap;\nimport static org.hamcrest.Matchers.*;\nimport static org.hamcrest.core.AllOf.allOf;\nimport static org.hamcrest.core.IsNot.not;\nimport static org.junit.Assert.*;\nimport static org.junit.Assume.assumeNotNull;\n\n@SuppressWarnings({\"rawtypes\", \"unchecked\", \"JavaDoc\"})\nabstract public class AbstractApiTest extends MockableTest {\n    private static final String API_TEST = \"api_test_\" + SUFFIX;\n    private static final String API_TEST_1 = API_TEST + \"_1\";\n    private static final String API_TEST_2 = API_TEST + \"_2\";\n    private static final String API_TEST_3 = API_TEST + \"_3\";\n    private static final String API_TEST_5 = API_TEST + \"_5\";\n    public static final String API_TEST_TRANSFORMATION = \"api_test_transformation_\" + SUFFIX;\n    public static final String API_TEST_TRANSFORMATION_2 = API_TEST_TRANSFORMATION + \"2\";\n    public static final String API_TEST_TRANSFORMATION_3 = API_TEST_TRANSFORMATION + \"3\";\n    public static final String API_TEST_UPLOAD_PRESET = \"api_test_upload_preset_\" + SUFFIX;\n    public static final String API_TEST_UPLOAD_PRESET_2 = API_TEST_UPLOAD_PRESET + \"2\";\n    public static final String API_TEST_UPLOAD_PRESET_3 = API_TEST_UPLOAD_PRESET + \"3\";\n    public static final String API_TEST_UPLOAD_PRESET_4 = API_TEST_UPLOAD_PRESET + \"4\";\n    public static final String API_TAG = SDK_TEST_TAG + \"_api\";\n    public static final String DIRECTION_TAG = SDK_TEST_TAG + \"_api_resource_direction\";\n    public static final String[] UPLOAD_TAGS = {SDK_TEST_TAG, API_TAG};\n    public static final String EXPLICIT_TRANSFORMATION_NAME = \"c_scale,l_text:Arial_60:\" + SUFFIX + \",w_100\";\n    public static final Transformation EXPLICIT_TRANSFORMATION = new Transformation().width(100).crop(\"scale\").overlay(new TextLayer().text(SUFFIX).fontFamily(\"Arial\").fontSize(60));\n    public static final String UPDATE_TRANSFORMATION_NAME = \"c_scale,l_text:Arial_60:\" + SUFFIX + \"_update,w_100\";\n    public static final Transformation UPDATE_TRANSFORMATION = new Transformation().width(100).crop(\"scale\").overlay(new TextLayer().text(SUFFIX + \"_update\").fontFamily(\"Arial\").fontSize(60));\n    public static final String DELETE_TRANSFORMATION_NAME = \"c_scale,l_text:Arial_60:\" + SUFFIX + \"_delete,w_100\";\n    public static final Transformation DELETE_TRANSFORMATION = new Transformation().width(100).crop(\"scale\").overlay(new TextLayer().text(SUFFIX + \"_delete\").fontFamily(\"Arial\").fontSize(60));\n    public static final String TEST_KEY = \"test-key\" + SUFFIX;\n    public static final String API_TEST_RESTORE = \"api_test_restore\" + SUFFIX;\n    public static final Set<String> createdFolders = new HashSet<String>();\n    private static final String CUSTOM_USER_AGENT_PREFIX = \"TEST_USER_AGENT\";\n    private static final String CUSTOM_USER_AGENT_VERSION = \"9.9.9\";\n    private static String assetId1;\n    private static String assetId2;\n    private static String assetId3;\n\n    private static final int SLEEP_TIMEOUT = 5000;\n\n\n    protected Api api;\n\n    @BeforeClass\n    public static void setUpClass() throws IOException {\n        Cloudinary cloudinary = new Cloudinary();\n        if (cloudinary.config.apiSecret == null) {\n            System.err.println(\"Please setup environment for Upload test to run\");\n            return;\n        }\n\n        List<String> uploadAndDirectionTag = new ArrayList<String>(Arrays.asList(UPLOAD_TAGS));\n        uploadAndDirectionTag.add(DIRECTION_TAG);\n\n        Map options = ObjectUtils.asMap(\"public_id\", API_TEST, \"tags\", uploadAndDirectionTag, \"context\", \"key=value\", \"eager\",\n                Collections.singletonList(EXPLICIT_TRANSFORMATION));\n        assetId1 = cloudinary.uploader().upload(SRC_TEST_IMAGE, options).get(\"asset_id\").toString();\n\n        options.put(\"public_id\", API_TEST_1);\n        assetId2 = cloudinary.uploader().upload(SRC_TEST_IMAGE, options).get(\"asset_id\").toString();\n        options.remove(\"public_id\");\n\n        assetId3 = cloudinary.uploader().upload(SRC_TEST_IMAGE, ObjectUtils.asMap(\"asset_folder\", \"test_asset_folder\")).get(\"public_id\").toString();\n\n        options.put(\"eager\", Collections.singletonList(UPDATE_TRANSFORMATION));\n        cloudinary.uploader().upload(SRC_TEST_IMAGE, options);\n\n        options.put(\"eager\", Collections.singletonList(DELETE_TRANSFORMATION));\n        cloudinary.uploader().upload(SRC_TEST_IMAGE, options);\n\n        String context1 = TEST_KEY + \"=alt\";\n        String context2 = TEST_KEY + \"=alternate\";\n\n        options = ObjectUtils.asMap(\"public_id\", \"context_1\" + SUFFIX, \"tags\", uploadAndDirectionTag, \"context\", context1);\n        cloudinary.uploader().upload(SRC_TEST_IMAGE, options);\n\n        options = ObjectUtils.asMap(\"public_id\", \"context_2\" + SUFFIX, \"tags\", uploadAndDirectionTag, \"context\", context2);\n        cloudinary.uploader().upload(SRC_TEST_IMAGE, options);\n    }\n\n    @AfterClass\n    public static void tearDownClass() {\n        Api api = new Cloudinary().api();\n        try {\n            api.deleteResourcesByTag(API_TAG, ObjectUtils.emptyMap());\n        } catch (Exception ignored) {\n        }\n        try {\n            api.deleteTransformation(API_TEST_TRANSFORMATION, ObjectUtils.emptyMap());\n        } catch (Exception ignored) {\n        }\n        try {\n            api.deleteTransformation(API_TEST_TRANSFORMATION_2, ObjectUtils.emptyMap());\n        } catch (Exception ignored) {\n        }\n        try {\n            api.deleteTransformation(API_TEST_TRANSFORMATION_3, ObjectUtils.emptyMap());\n        } catch (Exception ignored) {\n        }\n        try {\n            api.deleteUploadPreset(API_TEST_UPLOAD_PRESET, ObjectUtils.emptyMap());\n        } catch (Exception ignored) {\n        }\n        try {\n            api.deleteUploadPreset(API_TEST_UPLOAD_PRESET_2, ObjectUtils.emptyMap());\n        } catch (Exception ignored) {\n        }\n        try {\n            api.deleteUploadPreset(API_TEST_UPLOAD_PRESET_3, ObjectUtils.emptyMap());\n        } catch (Exception ignored) {\n        }\n        try {\n            api.deleteUploadPreset(API_TEST_UPLOAD_PRESET_4, ObjectUtils.emptyMap());\n        } catch (Exception ignored) {\n        }\n        try {\n            for (String folder : createdFolders) {\n                api.deleteFolder(folder, ObjectUtils.emptyMap());\n            }\n        } catch (Exception ignored) {\n        }\n    }\n\n    @Rule\n    public TestName currentTest = new TestName();\n\n    @Rule\n    public RetryRule retryRule = new RetryRule();\n\n    @Before\n    public void setUp() {\n        System.out.println(\"Running \" + this.getClass().getName() + \".\" + currentTest.getMethodName());\n        this.cloudinary = new Cloudinary();\n        assumeNotNull(cloudinary.config.apiSecret);\n        this.api = cloudinary.api();\n\n\n    }\n\n    public Map findByAttr(List<Map> elements, String attr, Object value) {\n        for (Map element : elements) {\n            if (value.equals(element.get(attr))) {\n                return element;\n            }\n        }\n        return null;\n    }\n\n    @Test\n    public void testCustomUserAgent() throws Exception {\n        // should allow setting a custom user-agent\n        cloudinary.setUserAgent(CUSTOM_USER_AGENT_PREFIX, CUSTOM_USER_AGENT_VERSION);\n        Map results = api.ping(ObjectUtils.emptyMap());\n        //TODO Mock server and assert the header\n    }\n\n    @Test\n    public void test01ResourceTypes() throws Exception {\n        // should allow listing resource_types\n        Map result = api.resourceTypes(ObjectUtils.emptyMap());\n        final List<String> resource_types = (List<String>) result.get(\"resource_types\");\n        assertThat(resource_types, hasItem(\"image\"));\n    }\n\n    @Test\n    public void testSingleSelectiveResponse() throws Exception {\n        Map options = new HashMap();\n        options.put(\"fields\", \"width\");\n        Map result = api.resources(options);\n        List<Map> resources = (List<Map>) result.get(\"resources\");\n        assertNotNull(resources);\n        Map resource = resources.get(0);\n        assertNotNull(resource);\n        assertNotNull(resource.get(\"width\"));\n        assertNull(resource.get(\"format\"));\n    }\n\n    @Test\n    public void testMultipleSelectiveResponse() throws Exception {\n        Map options = new HashMap();\n        options.put(\"fields\", new String[]{\"width\", \"format\"});\n        Map result = api.resources(options);\n        List<Map> resources = (List<Map>) result.get(\"resources\");\n        assertNotNull(resources);\n        Map resource = resources.get(0);\n        assertNotNull(resource);\n        assertNotNull(resource.get(\"width\"));\n        assertNotNull(resource.get(\"format\"));\n        assertNull(resource.get(\"height\"));\n    }\n\n    @Test\n    public void test03ResourcesCursor() throws Exception {\n        // should allow listing resources with cursor\n        Map options = new HashMap();\n        options.put(\"max_results\", 1);\n        Map result = api.resources(options);\n        List<Map> resources = (List<Map>) result.get(\"resources\");\n        assertNotNull(resources);\n        assertEquals(1, resources.size());\n        assertNotNull(result.get(\"next_cursor\"));\n\n        options.put(\"next_cursor\", result.get(\"next_cursor\"));\n        Map result2 = api.resources(options);\n        List<Map> resources2 = (List<Map>) result2.get(\"resources\");\n        assertNotNull(resources2);\n        assertEquals(resources2.size(), 1);\n        assertNotSame(resources2.get(0).get(\"public_id\"), resources.get(0).get(\"public_id\"));\n    }\n\n    @Test\n    public void test04ResourcesByType() throws Exception {\n        // should allow listing resources by type\n        Map result = api.resources(ObjectUtils.asMap(\"type\", \"upload\", \"max_results\", 10));\n        List<Map> resources = (List) result.get(\"resources\");\n\n        // beforeClass hook uploads several type:upload resources, we can rely on it.\n        assertTrue(resources.size() > 0);\n    }\n\n    @Test\n    public void testOAuthToken() {\n        String message = \"\";\n        try {\n            api.resource(API_TEST, Collections.singletonMap(\"oauth_token\", \"not_a_real_token\"));\n        } catch (Exception e) {\n            message = e.getMessage();\n        }\n\n        assertTrue(message.contains(\"Invalid token\"));\n    }\n\n    @Test\n    public void test05ResourcesByPrefix() throws Exception {\n        // should allow listing resources by prefix\n        Map result = api.resources(ObjectUtils.asMap(\"type\", \"upload\", \"prefix\", API_TEST, \"tags\", true, \"context\", true));\n        List<Map> resources = (List) result.get(\"resources\");\n        assertThat(resources, hasItem(hasEntry(\"public_id\", (Object) API_TEST)));\n        assertThat(resources, hasItem(hasEntry(\"public_id\", (Object) API_TEST_1)));\n//        resources = (List<Map<? extends String, ?>>) result.get(\"resources\");\n        assertThat(resources, hasItem(allOf(hasEntry(\"public_id\", API_TEST), hasEntry(\"type\", \"upload\"))));\n        assertThat(resources, hasItem(hasEntry(\"context\", ObjectUtils.asMap(\"custom\", ObjectUtils.asMap(\"key\", \"value\")))));\n        assertThat(resources, hasItem(hasEntry(equalTo(\"tags\"), hasItem(API_TAG))));\n    }\n\n    @Test\n    public void testResourcesListingDirection() throws Exception {\n        // should allow listing resources in both directions\n        Map result = api.resourcesByTag(DIRECTION_TAG, ObjectUtils.asMap(\"type\", \"upload\", \"direction\", \"asc\", \"max_results\", 500));\n        List<Map> resources = (List<Map>) result.get(\"resources\");\n        ArrayList<String> resourceIds = new ArrayList<String>();\n        for (Map resource : resources) {\n            resourceIds.add((String) resource.get(\"public_id\"));\n        }\n        result = api.resourcesByTag(DIRECTION_TAG, ObjectUtils.asMap(\"type\", \"upload\", \"direction\", -1, \"max_results\", 500));\n        List<Map> resourcesDesc = (List<Map>) result.get(\"resources\");\n        ArrayList<String> resourceIdsDesc = new ArrayList<String>();\n        for (Map resource : resourcesDesc) {\n            resourceIdsDesc.add((String) resource.get(\"public_id\"));\n        }\n        Collections.reverse(resourceIds);\n        assertEquals(resourceIds, resourceIdsDesc);\n    }\n\n    @Ignore\n    public void testResourcesListingStartAt() throws Exception {\n        // should allow listing resources by start date - make sure your clock\n        // is set correctly!!!\n        Thread.sleep(2000L);\n        java.util.Date startAt = new java.util.Date();\n        Thread.sleep(2000L);\n        Map response = cloudinary.uploader().upload(SRC_TEST_IMAGE, ObjectUtils.asMap(\"tags\", UPLOAD_TAGS));\n        ApiResponse listResources = api.resources(ObjectUtils.asMap(\"type\", \"upload\", \"start_at\", startAt, \"direction\", \"asc\"));\n        List<Map> resources = (List<Map>) listResources.get(\"resources\");\n        assertEquals(response.get(\"public_id\"), resources.get(0).get(\"public_id\"));\n    }\n\n    @Test\n    public void testTransformationsWithCursor() throws Exception {\n        String name = \"testTransformation\" + SDK_TEST_TAG + System.currentTimeMillis();\n        api.createTransformation(name, \"c_scale,w_100\", null);\n        final List<Map> transformations = new ArrayList<Map>();\n        String next_cursor = null;\n        do {\n            Map result = api.transformations(ObjectUtils.asMap(\"max_results\", 500, \"next_cursor\", next_cursor));\n            transformations.addAll((List) result.get(\"transformations\"));\n            next_cursor = (String) result.get(\"next_cursor\");\n        } while (next_cursor != null);\n        assertThat(transformations, hasItem(allOf(hasEntry(\"name\", \"t_\" + name))));\n    }\n\n    @Test\n    public void testResourcesByAssetIds() throws Exception {\n        Map result = api.resourcesByAssetIDs(Arrays.asList(assetId1, assetId2), ObjectUtils.asMap(\"tags\", true, \"context\", true));\n        List<Map> resources = (List<Map>) result.get(\"resources\");\n        assertEquals(2, resources.size());\n        assertNotNull(findByAttr(resources, \"public_id\", API_TEST));\n        assertNotNull(findByAttr(resources, \"public_id\", API_TEST_1));\n    }\n\n    @Test\n    public void testResourceByAssetId() throws Exception {\n        Map result = api.resourceByAssetID(assetId1, ObjectUtils.asMap(\"tags\", true, \"context\", true));\n        assertEquals(API_TEST, result.get(\"public_id\").toString());\n    }\n\n    @Test\n    public void testResourceByAssetFolder() throws Exception {\n        if (MockableTest.shouldTestFeature(Feature.DYNAMIC_FOLDERS)) {\n            Map result = api.resourcesByAssetFolder(\"test_asset_folder\", ObjectUtils.asMap(\"tags\", true, \"context\", true));\n            assertNotNull(findByAttr((List<Map>) result.get(\"resources\"), \"public_id\", assetId3));\n        }\n    }\n\n    @Test\n    public void testResourcesByPublicIds() throws Exception {\n        // should allow listing resources by public ids\n        Map result = api.resourcesByIds(Arrays.asList(API_TEST, API_TEST_1, \"bogus\"), ObjectUtils.asMap(\"type\", \"upload\", \"tags\", true, \"context\", true));\n        List<Map> resources = (List<Map>) result.get(\"resources\");\n        assertEquals(2, resources.size());\n        assertNotNull(findByAttr(resources, \"public_id\", API_TEST));\n        assertNotNull(findByAttr(resources, \"public_id\", API_TEST_1));\n        assertNotNull(findByAttr((List<Map>) result.get(\"resources\"), \"context\", ObjectUtils.asMap(\"custom\", ObjectUtils.asMap(\"key\", \"value\"))));\n        boolean found = false;\n        for (Map r : resources) {\n            ArrayList tags = (ArrayList) r.get(\"tags\");\n            found = found || tags.contains(API_TAG);\n        }\n        assertTrue(found);\n    }\n\n    @Test\n    public void test06ResourcesTag() throws Exception {\n        // should allow listing resources by tag\n        Map result = api.resourcesByTag(API_TAG, ObjectUtils.asMap(\"tags\", true, \"context\", true, \"max_results\", 500));\n        Map resource = findByAttr((List<Map>) result.get(\"resources\"), \"public_id\", API_TEST);\n        assertNotNull(resource);\n        resource = findByAttr((List<Map>) result.get(\"resources\"), \"context\", ObjectUtils.asMap(\"custom\", ObjectUtils.asMap(\"key\", \"value\")));\n        assertNotNull(resource);\n        List<Map> resources = (List<Map>) result.get(\"resources\");\n        boolean found = false;\n        for (Map r : resources) {\n            ArrayList tags = (ArrayList) r.get(\"tags\");\n            found = found || tags.contains(API_TAG);\n        }\n        assertTrue(found);\n    }\n\n    @Test\n    public void test07ResourceMetadata() throws Exception {\n        // should allow get resource metadata\n        Map resource = api.resource(API_TEST, ObjectUtils.emptyMap());\n        assertNotNull(resource);\n        assertEquals(API_TEST, resource.get(\"public_id\"));\n        assertEquals(3381, resource.get(\"bytes\"));\n        assertEquals(1, ((List) resource.get(\"derived\")).size());\n    }\n\n    @Test\n    public void test08DeleteDerived() throws Exception {\n        // should allow deleting derived resource\n        cloudinary.uploader().upload(SRC_TEST_IMAGE,\n                ObjectUtils.asMap(\"public_id\", API_TEST_3, \"tags\", UPLOAD_TAGS, \"eager\", Collections.singletonList(new Transformation().width(101).crop(\"scale\"))));\n        Map resource = api.resource(API_TEST_3, ObjectUtils.emptyMap());\n        assertNotNull(resource);\n        List<Map> derived = (List<Map>) resource.get(\"derived\");\n        assertEquals(derived.size(), 1);\n        String derived_resource_id = (String) derived.get(0).get(\"id\");\n        api.deleteDerivedResources(Collections.singletonList(derived_resource_id), ObjectUtils.emptyMap());\n        resource = api.resource(API_TEST_3, ObjectUtils.emptyMap());\n        assertNotNull(resource);\n        derived = (List<Map>) resource.get(\"derived\");\n        assertEquals(derived.size(), 0);\n    }\n\n    @Test()\n    public void testDeleteDerivedByTransformation() throws Exception {\n        // should allow deleting resources\n        String public_id = \"api_test_123\" + SUFFIX;\n        List<Transformation> transformations = new ArrayList<Transformation>();\n        transformations.add(new Transformation().angle(90));\n        transformations.add(new Transformation().width(120));\n        cloudinary.uploader().upload(SRC_TEST_IMAGE, ObjectUtils.asMap(\"public_id\", public_id, \"tags\", UPLOAD_TAGS, \"eager\", transformations));\n        Map resource = api.resource(public_id, ObjectUtils.emptyMap());\n        assertNotNull(resource);\n        List derived = ((List) resource.get(\"derived\"));\n        assertTrue(derived.size() == 2);\n        api.deleteDerivedByTransformation(ObjectUtils.asArray(public_id), ObjectUtils.asArray(transformations), ObjectUtils.emptyMap());\n\n        resource = api.resource(public_id, ObjectUtils.emptyMap());\n        assertNotNull(resource);\n        derived = ((List) resource.get(\"derived\"));\n        assertTrue(derived.size() == 0);\n    }\n\n    @Test\n    public void testGetResourcesWithMetadata() throws Exception {\n        String public_id = \"api_,withMetadata\" + SUFFIX;\n        String fieldId = MetadataTestHelper.addFieldToAccount(api, MetadataTestHelper.newFieldInstance(\"some_field\" + SUFFIX, true)).get(\"external_id\").toString();\n        cloudinary.uploader().upload(SRC_TEST_IMAGE, \n                ObjectUtils.asMap(\"public_id\", public_id, \n                    \"tags\", UPLOAD_TAGS, \n                    \"metadata\", ObjectUtils.asMap(fieldId, \"test\"), \n                    \"moderation\", \"manual\", \n                    \"context\", ObjectUtils.asMap(\"name\", \"value\")));\n        \n        Map result = api.resources(ObjectUtils.asMap(\"metadata\", false));\n        assertNull(getMetadata(public_id, result));\n        \n        result = api.resources(ObjectUtils.asMap(\"metadata\", true));\n        assertNotNull(getMetadata(public_id, result));\n        \n        result = api.resourcesByTag(UPLOAD_TAGS[0], ObjectUtils.asMap(\"metadata\", true));\n        assertNotNull(getMetadata(public_id, result));\n        \n        result = api.resourcesByTag(UPLOAD_TAGS[0], ObjectUtils.asMap(\"metadata\", false));\n        assertNull(getMetadata(public_id, result));\n        \n        result = api.resourcesByModeration(\"manual\", \"pending\", ObjectUtils.asMap(\"metadata\", true));\n        assertNotNull(getMetadata(public_id, result));\n        \n        result = api.resourcesByModeration(\"manual\", \"pending\", ObjectUtils.asMap(\"metadata\", false));\n        assertNull(getMetadata(public_id, result));\n        \n        result = api.resourcesByContext(\"name\", \"value\", ObjectUtils.asMap(\"metadata\", true));\n        assertNotNull(getMetadata(public_id, result));\n        \n        result = api.resourcesByContext(\"name\", \"value\", ObjectUtils.asMap(\"metadata\", false));\n        assertNull(getMetadata(public_id, result));\n    }\n\n    private Object getMetadata(String public_id, Map result) {\n        Map resource = findByAttr((List<Map>) result.get(\"resources\"), \"public_id\", public_id);\n        return resource.get(\"metadata\");\n    }\n\n    @Test(expected = NotFound.class)\n    public void test09DeleteResources() throws Exception {\n        // should allow deleting resources\n        String public_id = \"api_,test3\" + SUFFIX;\n        cloudinary.uploader().upload(SRC_TEST_IMAGE, ObjectUtils.asMap(\"public_id\", public_id, \"tags\", UPLOAD_TAGS));\n        Map resource = api.resource(public_id, ObjectUtils.emptyMap());\n        assertNotNull(resource);\n        api.deleteResources(Arrays.asList(public_id), ObjectUtils.emptyMap());\n        api.resource(public_id, ObjectUtils.emptyMap());\n    }\n\n    @Test(expected = NotFound.class)\n    public void test10DeleteResourcesByAssetsIds() throws Exception {\n        String public_id = \"api_,test4\" + SUFFIX;\n        cloudinary.uploader().upload(SRC_TEST_IMAGE, ObjectUtils.asMap(\"public_id\", public_id, \"tags\", UPLOAD_TAGS));\n        Map resource = api.resource(public_id, ObjectUtils.emptyMap());\n        assertNotNull(resource);\n        String assetId = (String)resource.get(\"asset_id\");\n        ApiResponse response = api.deleteResourcesByAssetIds(Arrays.asList(assetId), ObjectUtils.emptyMap());\n        assertNotNull(response);\n        assertNotNull(response.get(\"deleted\"));\n        assertNotNull(response.get(\"deleted_counts\"));\n        api.resource(public_id, ObjectUtils.emptyMap());\n    }\n\n    @Test(expected = NotFound.class)\n    public void test09aDeleteResourcesByPrefix() throws Exception {\n        // should allow deleting resources\n        String public_id = SUFFIX + \"_api_test_by_prefix\";\n        cloudinary.uploader().upload(SRC_TEST_IMAGE, ObjectUtils.asMap(\"public_id\", public_id, \"tags\", UPLOAD_TAGS));\n        Map resource = api.resource(public_id, ObjectUtils.emptyMap());\n        assertNotNull(resource);\n        api.deleteResourcesByPrefix(public_id.substring(0, SUFFIX.length() + 10), ObjectUtils.emptyMap());\n        api.resource(public_id, ObjectUtils.emptyMap());\n    }\n\n    @Test(expected = NotFound.class)\n    public void test09aDeleteResourcesByTags() throws Exception {\n        // should allow deleting resources\n        String tag = \"api_test_tag_for_delete\" + SUFFIX;\n        cloudinary.uploader().upload(SRC_TEST_IMAGE, ObjectUtils.asMap(\"public_id\", API_TEST + \"_4\", \"tags\", Collections.singletonList(tag)));\n        Map resource = api.resource(API_TEST + \"_4\", ObjectUtils.emptyMap());\n        assertNotNull(resource);\n        api.deleteResourcesByTag(tag, ObjectUtils.emptyMap());\n        api.resource(API_TEST + \"_4\", ObjectUtils.emptyMap());\n    }\n\n    @Test\n    public void test10Tags() throws Exception {\n        // should allow listing tags\n        Map result = api.tags(ObjectUtils.asMap(\"max_results\", 10));\n        List<String> tags = (List<String>) result.get(\"tags\");\n        assertNotNull(tags);\n        assertTrue(tags.size() > 0);\n    }\n\n    @Test\n    public void test11TagsPrefix() throws Exception {\n        // should allow listing tag by prefix\n        Map result = api.tags(ObjectUtils.asMap(\"prefix\", API_TAG.substring(0, API_TAG.length() - 1)));\n        List<String> tags = (List<String>) result.get(\"tags\");\n        assertThat(tags, hasItem(API_TAG));\n        result = api.tags(ObjectUtils.asMap(\"prefix\", \"api_test_no_such_tag\"));\n        tags = (List<String>) result.get(\"tags\");\n        assertEquals(0, tags.size());\n    }\n\n    @Test\n    public void test12Transformations() throws Exception {\n        // should allow listing transformations\n        final Transformation listTest = new Transformation().width(25).crop(\"scale\").overlay(new TextLayer().text(SUFFIX + \"_testListTransformations\").fontFamily(\"Arial\").fontSize(60));\n        preloadResource(ObjectUtils.asMap(\"tags\", UPLOAD_TAGS, \"eager\", Collections.singletonList(listTest)));\n        Map result = api.transformations(ObjectUtils.asMap(\"max_results\", 500));\n        Map transformation = findByAttr((List<Map>) result.get(\"transformations\"), \"name\", listTest.generate());\n\n        assertNotNull(transformation);\n        assertTrue((Boolean) transformation.get(\"used\"));\n    }\n\n    @Test\n    public void test13TransformationMetadata() throws Exception {\n        // should allow getting transformation metadata\n        preloadResource(ObjectUtils.asMap(\"tags\", UPLOAD_TAGS, \"eager\", Collections.singletonList(EXPLICIT_TRANSFORMATION)));\n        Map transformation = api.transformation(EXPLICIT_TRANSFORMATION_NAME, ObjectUtils.asMap(\"max_results\", 500));\n        assertNotNull(transformation);\n        assertEquals(new Transformation((List<Map>) transformation.get(\"info\")).generate(), EXPLICIT_TRANSFORMATION.generate());\n    }\n\n    @Test\n    public void test14TransformationUpdate() throws Exception {\n        // should allow updating transformation allowed_for_strict\n        api.updateTransformation(UPDATE_TRANSFORMATION_NAME, ObjectUtils.asMap(\"allowed_for_strict\", true), ObjectUtils.emptyMap());\n        Map transformation = api.transformation(UPDATE_TRANSFORMATION_NAME, ObjectUtils.emptyMap());\n        assertNotNull(transformation);\n        assertEquals(transformation.get(\"allowed_for_strict\"), true);\n        api.updateTransformation(UPDATE_TRANSFORMATION_NAME, ObjectUtils.asMap(\"allowed_for_strict\", false), ObjectUtils.emptyMap());\n        transformation = api.transformation(UPDATE_TRANSFORMATION_NAME, ObjectUtils.emptyMap());\n        assertNotNull(transformation);\n        assertEquals(transformation.get(\"allowed_for_strict\"), false);\n    }\n\n    @Test\n    public void test15TransformationCreate() throws Exception {\n        // should allow creating named transformation\n        api.createTransformation(API_TEST_TRANSFORMATION, new Transformation().crop(\"scale\").width(102).generate(), ObjectUtils.emptyMap());\n        Map transformation = api.transformation(API_TEST_TRANSFORMATION, ObjectUtils.emptyMap());\n        assertNotNull(transformation);\n        assertEquals(transformation.get(\"allowed_for_strict\"), true);\n        assertEquals(new Transformation((List<Map>) transformation.get(\"info\")).generate(), new Transformation().crop(\"scale\").width(102).generate());\n        assertEquals(transformation.get(\"used\"), false);\n    }\n\n    @Test\n    public void test15aTransformationUnsafeUpdate() throws Exception {\n        // should allow unsafe update of named transformation\n        api.createTransformation(API_TEST_TRANSFORMATION_3, new Transformation().crop(\"scale\").width(102).generate(), ObjectUtils.emptyMap());\n        api.updateTransformation(API_TEST_TRANSFORMATION_3, ObjectUtils.asMap(\"unsafe_update\", new Transformation().crop(\"scale\").width(103).generate()),\n                ObjectUtils.emptyMap());\n        Map transformation = api.transformation(API_TEST_TRANSFORMATION_3, ObjectUtils.emptyMap());\n        assertNotNull(transformation);\n        assertEquals(new Transformation((List<Map>) transformation.get(\"info\")).generate(), new Transformation().crop(\"scale\").width(103).generate());\n        assertEquals(transformation.get(\"used\"), false);\n    }\n\n    @Test(expected = NotFound.class)\n    public void test16aTransformationDelete() throws Exception {\n        // should allow deleting named transformation\n        api.createTransformation(API_TEST_TRANSFORMATION_2, new Transformation().crop(\"scale\").width(103).generate(), ObjectUtils.emptyMap());\n        api.transformation(API_TEST_TRANSFORMATION_2, ObjectUtils.emptyMap());\n        ApiResponse res = api.deleteTransformation(API_TEST_TRANSFORMATION_2, ObjectUtils.emptyMap());\n        assertEquals(\"deleted\", res.get(\"message\"));\n        api.transformation(API_TEST_TRANSFORMATION_2, ObjectUtils.emptyMap());\n    }\n\n    @Test(expected = NotFound.class)\n    public void test17aTransformationDeleteImplicit() throws Exception {\n        // should allow deleting implicit transformation\n        api.transformation(DELETE_TRANSFORMATION_NAME, ObjectUtils.emptyMap());\n        ApiResponse res = api.deleteTransformation(DELETE_TRANSFORMATION_NAME, ObjectUtils.emptyMap());\n        assertEquals(\"deleted\", res.get(\"message\"));\n        api.deleteTransformation(DELETE_TRANSFORMATION_NAME, ObjectUtils.emptyMap());\n    }\n\n    @Test\n    public void testListTransformationByNamed() throws Exception {\n        String name = \"a_test_named_transformation_param\" + SUFFIX;\n        try {\n            api.createTransformation(name, \"w_100\", null);\n            name = \"t_\" + name;\n            List<Map> named = (List) api.transformations(ObjectUtils.asMap(\"max_results\", 30, \"named\", true)).get(\"transformations\");\n            List<Map> unnamed = (List) api.transformations(ObjectUtils.asMap(\"max_results\", 30, \"named\", false)).get(\"transformations\");\n\n            // the named transformation should be present only in the named list:\n            boolean unnamedFound = false;\n            boolean namedFound = false;\n\n            for (Map t : unnamed) {\n                if (t.get(\"name\").equals(name)) {\n                    unnamedFound = true;\n                    break;\n                }\n            }\n\n            if (!unnamedFound) {\n                for (Map t : named) {\n                    if (t.get(\"name\").equals(name)) {\n                        namedFound = true;\n                        break;\n                    }\n                }\n            }\n\n            assertTrue(\"Named transformation wasn't returned with named=true param\", namedFound);\n            assertFalse(\"Named transformation returned with named=false param\", unnamedFound);\n\n        } finally {\n            try {\n                api.deleteTransformation(name, null);\n            } catch (Exception ignored) {\n            }\n        }\n    }\n\n    @Test\n    public void test20ResourcesContext() throws Exception {\n        Map result = api.resourcesByContext(TEST_KEY, ObjectUtils.emptyMap());\n\n        List<Map> resources = (List<Map>) result.get(\"resources\");\n        assertEquals(2, resources.size());\n        result = api.resourcesByContext(TEST_KEY, \"alt\", ObjectUtils.emptyMap());\n\n        resources = (List<Map>) result.get(\"resources\");\n        assertEquals(1, resources.size());\n    }\n\n    @Test\n    public void test18Usage() throws Exception {\n        // should support usage API call\n        final Date yesterday = yesterday();\n\n        Map result = api.usage(ObjectUtils.asMap(\"date\", yesterday));\n        assertNotNull(result.get(\"last_updated\"));\n\n        result = api.usage(ObjectUtils.asMap(\"date\", ObjectUtils.toUsageApiDateFormat(yesterday)));\n        assertNotNull(result.get(\"last_updated\"));\n\n        result = api.usage(ObjectUtils.emptyMap());\n        assertNotNull(result.get(\"last_updated\"));\n    }\n\n    private Date yesterday() {\n        return new Date(new Date().getTime() - 24 * 60 * 60 * 1000);\n    }\n\n    @Test\n    public void testRateLimitWithNonEnglishLocale() throws Exception {\n        Locale.setDefault(new Locale(\"de\", \"DE\"));\n        ApiResponse result = cloudinary.api().usage(new HashMap());\n        Assert.assertNotNull(result.apiRateLimit().getReset());\n    }\n\n    @Test\n    public void testRateLimits() throws Exception {\n        ApiResponse result = cloudinary.api().usage(new HashMap());\n        Assert.assertNotEquals(0, result.apiRateLimit().getLimit());\n        Assert.assertNotNull(result.apiRateLimit().getReset());\n        Assert.assertNotEquals(0, result.apiRateLimit().getRemaining());\n    }\n\n    @Test\n    public void testConfiguration() throws Exception {\n        ApiResponse result = cloudinary.api().configuration(ObjectUtils.asMap(\"settings\", true));\n        Map settings = (Map) result.get(\"settings\");\n        Assert.assertNotNull(settings.get(\"folder_mode\"));\n    }\n\n    @Test\n    public void test19Ping() throws Exception {\n        // should support ping API call\n        Map result = api.ping(ObjectUtils.emptyMap());\n        assertEquals(result.get(\"status\"), \"ok\");\n    }\n\n    // This test must be last because it deletes (potentially) all dependent\n    // transformations which some tests rely on.\n    // Add @Test if you really want to test it - This test deletes derived\n    // resources!\n    public void testDeleteAllResources() throws Exception {\n        // should allow deleting all resources\n        cloudinary.uploader().upload(SRC_TEST_IMAGE,\n                ObjectUtils.asMap(\"public_id\", API_TEST_5, \"tags\", UPLOAD_TAGS, \"eager\", Collections.singletonList(new Transformation().crop(\"scale\").width(2.0))));\n        Map result = api.resource(API_TEST_5, ObjectUtils.emptyMap());\n        assertEquals(1, ((org.cloudinary.json.JSONArray) result.get(\"derived\")).length());\n        api.deleteAllResources(ObjectUtils.asMap(\"keep_original\", true));\n        result = api.resource(API_TEST_5, ObjectUtils.emptyMap());\n        // assertEquals(0, ((org.cloudinary.json.JSONArray)\n        // result.get(\"derived\")).size());\n    }\n\n    @Test\n    public void testManualModeration() throws Exception {\n        // should support setting manual moderation status\n        Map uploadResult = cloudinary.uploader().upload(SRC_TEST_IMAGE, ObjectUtils.asMap(\"moderation\", \"manual\", \"tags\", UPLOAD_TAGS));\n        Map apiResult = api.update((String) uploadResult.get(\"public_id\"), ObjectUtils.asMap(\"moderation_status\", \"approved\", \"tags\", UPLOAD_TAGS));\n        assertEquals(\"approved\", ((Map) ((List<Map>) apiResult.get(\"moderation\")).get(0)).get(\"status\"));\n    }\n\n    @Test\n    public void testOcrUpdate() throws Exception {\n        assumeAddonEnabled(\"ocr\");\n        Exception expected = null;\n        // should support requesting ocr info\n        try {\n            Map uploadResult = cloudinary.uploader().upload(SRC_TEST_IMAGE, ObjectUtils.asMap(\"tags\", UPLOAD_TAGS));\n            api.update((String) uploadResult.get(\"public_id\"), ObjectUtils.asMap(\"ocr\", \"illegal\"));\n        } catch (Exception e) {\n            expected = e;\n        }\n\n        assertNotNull(expected);\n        assertTrue(expected instanceof BadRequest);\n        assertTrue(expected.getMessage().matches(\"^Illegal value(.*)\"));\n    }\n\n    @Test\n    public void testRawConvertUpdate() {\n        // should support requesting raw conversion\n        try {\n            Map uploadResult = cloudinary.uploader().upload(SRC_TEST_IMAGE, ObjectUtils.asMap(\"tags\", UPLOAD_TAGS));\n            api.update((String) uploadResult.get(\"public_id\"), ObjectUtils.asMap(\"raw_convert\", \"illegal\"));\n        } catch (Exception e) {\n            assertTrue(e instanceof BadRequest);\n            assertTrue(e.getMessage().matches(\"^Illegal value(.*)\"));\n        }\n    }\n\n    @Test\n    public void testCategorizationUpdate() {\n        // should support requesting categorization\n        try {\n            Map uploadResult = cloudinary.uploader().upload(SRC_TEST_IMAGE, ObjectUtils.asMap(\"tags\", UPLOAD_TAGS));\n            api.update((String) uploadResult.get(\"public_id\"), ObjectUtils.asMap(\"categorization\", \"illegal\"));\n        } catch (Exception e) {\n            assertTrue(e instanceof BadRequest);\n            assertTrue(e.getMessage().matches(\"^Illegal value(.*)\"));\n        }\n    }\n\n    @Test\n    public void testDetectionUpdate() {\n        // should support requesting detection\n        try {\n            Map uploadResult = cloudinary.uploader().upload(SRC_TEST_IMAGE, ObjectUtils.asMap(\"tags\", UPLOAD_TAGS));\n            api.update((String) uploadResult.get(\"public_id\"), ObjectUtils.asMap(\"detection\", \"illegal\"));\n        } catch (Exception e) {\n            assertTrue(e instanceof BadRequest);\n            assertTrue(e.getMessage().matches(\"^Illegal value(.*)\"));\n        }\n    }\n\n    @Test\n    public void testUpdateResourceClearInvalid() throws Exception {\n        String fieldId = MetadataTestHelper.addFieldToAccount(api, MetadataTestHelper.newFieldInstance(\"some_field3\" + SUFFIX, true)).get(\"external_id\").toString();\n        String fieldId2 = MetadataTestHelper.addFieldToAccount(api, MetadataTestHelper.newFieldInstance(\"some_field4\" + SUFFIX, true)).get(\"external_id\").toString();\n        Map uploadResult = cloudinary.uploader().upload(SRC_TEST_IMAGE,\n                ObjectUtils.asMap(\"tags\", UPLOAD_TAGS, \"metadata\", ObjectUtils.asMap(fieldId, \"test\")));\n        Map apiResult = api.update((String) uploadResult.get(\"public_id\"), ObjectUtils.asMap(\"clear_invalid\", true, \"metadata\", ObjectUtils.asMap(fieldId2, \"test2\")));\n        assertNotNull(((Map)apiResult.get(\"metadata\")).get(fieldId2));\n    }\n\n    @Test\n    public void testUpdateCustomCoordinates() throws IOException, Exception {\n        // should update custom coordinates\n        Coordinates coordinates = new Coordinates(\"121,31,110,151\");\n        Map uploadResult = cloudinary.uploader().upload(SRC_TEST_IMAGE, ObjectUtils.asMap(\"tags\", UPLOAD_TAGS));\n        cloudinary.api().update(uploadResult.get(\"public_id\").toString(), ObjectUtils.asMap(\"custom_coordinates\", coordinates));\n        Map result = cloudinary.api().resource(uploadResult.get(\"public_id\").toString(), ObjectUtils.asMap(\"coordinates\", true));\n        int[] expected = new int[]{121, 31, 110, 151};\n        ArrayList actual = (ArrayList) ((ArrayList) ((Map) result.get(\"coordinates\")).get(\"custom\")).get(0);\n        for (int i = 0; i < expected.length; i++) {\n            assertEquals(expected[i], actual.get(i));\n        }\n    }\n\n    @Test\n    public void testUpdateAccessControl() throws Exception {\n        // should update access control\n        SimpleDateFormat simpleDateFormat = new SimpleDateFormat(\"yyyy-MM-dd HH:mm:ss Z\");\n        final Date start = simpleDateFormat.parse(\"2019-02-22 16:20:57 +0200\");\n        final Date end = simpleDateFormat.parse(\"2019-03-22 00:00:00 +0200\");\n        AccessControlRule acl = AccessControlRule.anonymous(start, end);\n        Map uploadResult = cloudinary.uploader().upload(SRC_TEST_IMAGE, ObjectUtils.asMap(\"tags\", UPLOAD_TAGS));\n        ApiResponse res = cloudinary.api().update(uploadResult.get(\"public_id\").toString(), ObjectUtils.asMap(\"access_control\", acl));\n        Map result = cloudinary.api().resource(uploadResult.get(\"public_id\").toString(), ObjectUtils.asMap(\"access_control\", true));\n\n        Map accessControlResult = (Map) ((List) result.get(\"access_control\")).get(0);\n\n        assertEquals(\"anonymous\", accessControlResult.get(\"access_type\"));\n        assertEquals(\"2019-02-22T14:20:57Z\", accessControlResult.get(\"start\"));\n        assertEquals(\"2019-03-21T22:00:00Z\", accessControlResult.get(\"end\"));\n    }\n\n    @Test\n    public void testListUploadPresets() throws Exception {\n        // should allow creating and listing upload_presets\n        api.createUploadPreset(ObjectUtils.asMap(\"name\", API_TEST_UPLOAD_PRESET, \"folder\", \"folder\"));\n        api.createUploadPreset(ObjectUtils.asMap(\"name\", API_TEST_UPLOAD_PRESET_2, \"folder\", \"folder2\"));\n        api.createUploadPreset(ObjectUtils.asMap(\"name\", API_TEST_UPLOAD_PRESET_3, \"folder\", \"folder3\"));\n\n        ArrayList<Map> presets = (ArrayList) (api.uploadPresets(ObjectUtils.emptyMap()).get(\"presets\"));\n\n        assertThat(presets, hasItem(hasEntry(\"name\", API_TEST_UPLOAD_PRESET)));\n        assertThat(presets, hasItem(hasEntry(\"name\", API_TEST_UPLOAD_PRESET_2)));\n        assertThat(presets, hasItem(hasEntry(\"name\", API_TEST_UPLOAD_PRESET_3)));\n\n        api.deleteUploadPreset(API_TEST_UPLOAD_PRESET, ObjectUtils.emptyMap());\n        api.deleteUploadPreset(API_TEST_UPLOAD_PRESET_2, ObjectUtils.emptyMap());\n        api.deleteUploadPreset(API_TEST_UPLOAD_PRESET_3, ObjectUtils.emptyMap());\n    }\n\n    @Test\n    public void testGetUploadPreset() throws Exception {\n        // should allow getting a single upload_preset\n        String[] tags = {\"a\", \"b\", \"c\"};\n        Map context = ObjectUtils.asMap(\"a\", \"b\", \"c\", \"d\");\n        Map result = api.createUploadPreset(ObjectUtils.asMap(\"unsigned\", true, \"folder\", \"folder\", \"transformation\", EXPLICIT_TRANSFORMATION, \"tags\", tags, \"context\",\n                context, \"use_asset_folder_as_public_id_prefix\", true));\n        String name = result.get(\"name\").toString();\n        Map preset = api.uploadPreset(name, ObjectUtils.emptyMap());\n        assertEquals(preset.get(\"name\"), name);\n        assertEquals(Boolean.TRUE, preset.get(\"unsigned\"));\n        Map settings = (Map) preset.get(\"settings\");\n        assertEquals(settings.get(\"folder\"), \"folder\");\n        assertEquals(settings.get(\"use_asset_folder_as_public_id_prefix\"), true);\n        Map outTransformation = (Map) ((java.util.ArrayList) settings.get(\"transformation\")).get(0);\n        assertEquals(outTransformation.get(\"width\"), 100);\n        assertEquals(outTransformation.get(\"crop\"), \"scale\");\n        Object[] outTags = ((java.util.ArrayList) settings.get(\"tags\")).toArray();\n        assertArrayEquals(tags, outTags);\n        Map outContext = (Map) settings.get(\"context\");\n        assertEquals(context, outContext);\n\n        api.deleteUploadPreset(name, ObjectUtils.emptyMap());\n    }\n\n    @Test\n    public void testDeleteUploadPreset() throws Exception {\n        // should allow deleting upload_presets\", :upload_preset => true do\n        api.createUploadPreset(ObjectUtils.asMap(\"name\", API_TEST_UPLOAD_PRESET_4, \"folder\", \"folder\"));\n        api.uploadPreset(API_TEST_UPLOAD_PRESET_4, ObjectUtils.emptyMap());\n        api.deleteUploadPreset(API_TEST_UPLOAD_PRESET_4, ObjectUtils.emptyMap());\n        boolean error = false;\n        try {\n            api.uploadPreset(API_TEST_UPLOAD_PRESET_4, ObjectUtils.emptyMap());\n        } catch (Exception e) {\n            error = true;\n        }\n        assertTrue(error);\n    }\n\n    @Test\n    public void testUpdateUploadPreset() throws Exception {\n        // should allow updating upload_presets\n        String name = api.createUploadPreset(ObjectUtils.asMap(\"folder\", \"folder\")).get(\"name\").toString();\n        Map preset = api.uploadPreset(name, ObjectUtils.emptyMap());\n        Map settings = (Map) preset.get(\"settings\");\n        settings.putAll(ObjectUtils.asMap(\"colors\", true, \"unsigned\", true, \"disallow_public_id\", true, \"eval\",AbstractUploaderTest.SRC_TEST_EVAL));\n        api.updateUploadPreset(name, settings);\n        settings.remove(\"unsigned\");\n        preset = api.uploadPreset(name, ObjectUtils.emptyMap());\n        assertEquals(name, preset.get(\"name\"));\n        assertEquals(Boolean.TRUE, preset.get(\"unsigned\"));\n        assertEquals(settings, preset.get(\"settings\"));\n\n        api.deleteUploadPreset(name, ObjectUtils.emptyMap());\n    }\n\n    @Test\n    public void testListByModerationUpdate() throws Exception {\n        // \"should support listing by moderation kind and value\n        List<Map> resources;\n\n        Map result1 = cloudinary.uploader().upload(SRC_TEST_IMAGE, ObjectUtils.asMap(\"moderation\", \"manual\", \"tags\", UPLOAD_TAGS));\n        Map result2 = cloudinary.uploader().upload(SRC_TEST_IMAGE, ObjectUtils.asMap(\"moderation\", \"manual\", \"tags\", UPLOAD_TAGS));\n        Map result3 = cloudinary.uploader().upload(SRC_TEST_IMAGE, ObjectUtils.asMap(\"moderation\", \"manual\", \"tags\", UPLOAD_TAGS));\n        api.update((String) result1.get(\"public_id\"), ObjectUtils.asMap(\"moderation_status\", \"approved\"));\n        api.update((String) result2.get(\"public_id\"), ObjectUtils.asMap(\"moderation_status\", \"rejected\"));\n        Map approved = api.resourcesByModeration(\"manual\", \"approved\", ObjectUtils.asMap(\"max_results\", 1000));\n        Map rejected = api.resourcesByModeration(\"manual\", \"rejected\", ObjectUtils.asMap(\"max_results\", 1000));\n        Map pending = api.resourcesByModeration(\"manual\", \"pending\", ObjectUtils.asMap(\"max_results\", 1000));\n\n        resources = (List<Map>) approved.get(\"resources\");\n        assertThat(resources, hasItem(hasEntry(\"public_id\", result1.get(\"public_id\"))));\n        assertThat(resources, not(hasItem(hasEntry(\"public_id\", result2.get(\"public_id\")))));\n        assertThat(resources, not(hasItem(hasEntry(\"public_id\", result3.get(\"public_id\")))));\n\n        resources = (List<Map>) rejected.get(\"resources\");\n        assertThat(resources, not(hasItem(hasEntry(\"public_id\", result1.get(\"public_id\")))));\n        assertThat(resources, hasItem(hasEntry(\"public_id\", result2.get(\"public_id\"))));\n        assertThat(resources, not(hasItem(hasEntry(\"public_id\", result3.get(\"public_id\")))));\n\n        resources = (List<Map>) pending.get(\"resources\");\n        assertThat(resources, not(hasItem(hasEntry(\"public_id\", result1.get(\"public_id\")))));\n        assertThat(resources, not(hasItem(hasEntry(\"public_id\", result2.get(\"public_id\")))));\n        assertThat(resources, hasItem(hasEntry(\"public_id\", result3.get(\"public_id\"))));\n    }\n\n    // For this test to work, \"Auto-create folders\" should be enabled in the\n    // Upload Settings.\n    // Uncomment @Test if you really want to test it.\n    // @Test\n    public void testFolderApi() throws Exception {\n        // should allow deleting all resources\n        cloudinary.uploader().upload(SRC_TEST_IMAGE, ObjectUtils.asMap(\"public_id\", \"test_folder1/item\", \"tags\", UPLOAD_TAGS));\n        cloudinary.uploader().upload(SRC_TEST_IMAGE, ObjectUtils.asMap(\"public_id\", \"test_folder2/item\", \"tags\", UPLOAD_TAGS));\n        cloudinary.uploader().upload(SRC_TEST_IMAGE, ObjectUtils.asMap(\"public_id\", \"test_folder1/test_subfolder1/item\", \"tags\", UPLOAD_TAGS));\n        cloudinary.uploader().upload(SRC_TEST_IMAGE, ObjectUtils.asMap(\"public_id\", \"test_folder1/test_subfolder2/item\", \"tags\", UPLOAD_TAGS));\n        Map result = api.rootFolders(null);\n        assertEquals(\"test_folder1\", ((Map) ((org.cloudinary.json.JSONArray) result.get(\"folders\")).get(0)).get(\"name\"));\n        assertEquals(\"test_folder2\", ((Map) ((org.cloudinary.json.JSONArray) result.get(\"folders\")).get(1)).get(\"name\"));\n        result = api.subFolders(\"test_folder1\", null);\n        assertEquals(\"test_folder1/test_subfolder1\", ((Map) ((org.cloudinary.json.JSONArray) result.get(\"folders\")).get(0)).get(\"path\"));\n        assertEquals(\"test_folder1/test_subfolder2\", ((Map) ((org.cloudinary.json.JSONArray) result.get(\"folders\")).get(1)).get(\"path\"));\n        try {\n            api.subFolders(\"test_folder\", null);\n        } catch (Exception e) {\n            assertTrue(e instanceof NotFound);\n        }\n        api.deleteResourcesByPrefix(\"test_folder\", ObjectUtils.emptyMap());\n    }\n\n    @Test\n    public void testCreateFolder() throws Exception {\n        String apTestCreateFolder = \"api_test_create_folder\" + \"_\" + SUFFIX;\n        createdFolders.add(apTestCreateFolder);\n        Map result = api.createFolder(\"apTestCreateFolder\", null);\n        assertTrue((Boolean) result.get(\"success\"));\n    }\n\n    @Test\n    public void testRestore() throws Exception {\n        // should support restoring resources\n        cloudinary.uploader().upload(SRC_TEST_IMAGE,\n                ObjectUtils.asMap(\"public_id\", API_TEST_RESTORE, \"backup\", true, \"tags\", UPLOAD_TAGS));\n        Map resource = api.resource(API_TEST_RESTORE, ObjectUtils.emptyMap());\n        assertEquals(resource.get(\"bytes\"), 3381);\n        api.deleteResources(Collections.singletonList(API_TEST_RESTORE), ObjectUtils.emptyMap());\n        resource = api.resource(API_TEST_RESTORE, ObjectUtils.emptyMap());\n        assertEquals(resource.get(\"bytes\"), 0);\n        assertTrue((Boolean) resource.get(\"placeholder\"));\n        Map response = api.restore(Collections.singletonList(API_TEST_RESTORE), ObjectUtils.emptyMap());\n        Map info = (Map) response.get(API_TEST_RESTORE);\n        assertNotNull(info);\n        assertEquals(info.get(\"bytes\"), 3381);\n        resource = api.resource(API_TEST_RESTORE, ObjectUtils.emptyMap());\n        assertEquals(resource.get(\"bytes\"), 3381);\n    }\n\n    @Test\n    public void testRestoreByAssetIds() throws Exception {\n\n        // Upload\n        cloudinary.uploader().upload(SRC_TEST_IMAGE,\n                ObjectUtils.asMap(\"public_id\", API_TEST_RESTORE, \"backup\", true, \"tags\", UPLOAD_TAGS));\n        Map resource = api.resource(API_TEST_RESTORE, ObjectUtils.emptyMap());\n        assertEquals(resource.get(\"bytes\"), 3381);\n\n        //Delete\n        api.deleteResources(Collections.singletonList(API_TEST_RESTORE), ObjectUtils.emptyMap());\n        resource = api.resource(API_TEST_RESTORE, ObjectUtils.emptyMap());\n        String assetId = (String) resource.get(\"asset_id\");\n        assertEquals(resource.get(\"bytes\"), 0);\n        assertNotNull(assetId);\n        assertTrue((Boolean) resource.get(\"placeholder\"));\n\n        //Restore\n        Map response = api.restoreByAssetIds(Collections.singletonList(assetId), ObjectUtils.emptyMap());\n        Map info = (Map) response.get(assetId);\n        assertNotNull(info);\n        assertEquals(info.get(\"bytes\"), 3381);\n        resource = api.resource(API_TEST_RESTORE, ObjectUtils.emptyMap());\n        assertEquals(resource.get(\"bytes\"), 3381);\n    }\n\n    @Test\n    public void testRestoreDifferentVersionsOfDeletedAsset() throws Exception {\n        final String TEST_RESOURCE_PUBLIC_ID = \"api_test_restore_different_versions_single_asset\" + SUFFIX;\n        final Uploader uploader = cloudinary.uploader();\n\n        Map firstUpload = uploader.upload(SRC_TEST_IMAGE,\n                ObjectUtils.asMap(\n                        \"public_id\", TEST_RESOURCE_PUBLIC_ID,\n                        \"backup\", true,\n                        \"tags\", UPLOAD_TAGS\n                ));\n        assertEquals(firstUpload.get(\"public_id\"), TEST_RESOURCE_PUBLIC_ID);\n        Thread.sleep(SLEEP_TIMEOUT);\n        ApiResponse firstDelete = api.deleteResources(Collections.singletonList(TEST_RESOURCE_PUBLIC_ID), ObjectUtils.emptyMap());\n        assertTrue(firstDelete.containsKey(\"deleted\"));\n        Thread.sleep(SLEEP_TIMEOUT);\n\n        Map secondUpload = uploader.upload(SRC_TEST_IMAGE,\n                ObjectUtils.asMap(\n                        \"public_id\", TEST_RESOURCE_PUBLIC_ID,\n                        \"backup\", true,\n                        \"transformation\", new Transformation().angle(\"0\"),\n                        \"tags\", UPLOAD_TAGS\n                ));\n        assertEquals(secondUpload.get(\"public_id\"), TEST_RESOURCE_PUBLIC_ID);\n        Thread.sleep(SLEEP_TIMEOUT);\n        ApiResponse secondDelete = api.deleteResources(Collections.singletonList(TEST_RESOURCE_PUBLIC_ID), ObjectUtils.emptyMap());\n        assertTrue(secondDelete.containsKey(\"deleted\"));\n        Thread.sleep(SLEEP_TIMEOUT);\n        assertNotEquals(firstUpload.get(\"bytes\"), secondUpload.get(\"bytes\"));\n\n        ApiResponse getVersionsResp = api.resource(TEST_RESOURCE_PUBLIC_ID, ObjectUtils.asMap(\"versions\", true));\n        List<Map> versions = (List<Map>) getVersionsResp.get(\"versions\");\n        Assert.assertTrue(versions.size() > 1);\n        Object firstAssetVersion = versions.get(0).get(\"version_id\");\n        Object secondAssetVersion = versions.get(1).get(\"version_id\");\n\n        ApiResponse firstVerRestore = api.restore(Collections.singletonList(TEST_RESOURCE_PUBLIC_ID),\n                ObjectUtils.asMap(\"versions\", Collections.singletonList(firstAssetVersion)));\n        assertEquals(((Map) firstVerRestore.get(TEST_RESOURCE_PUBLIC_ID)).get(\"bytes\"), firstUpload.get(\"bytes\"));\n\n        ApiResponse secondVerRestore = api.restore(Collections.singletonList(TEST_RESOURCE_PUBLIC_ID),\n                ObjectUtils.asMap(\"versions\", Collections.singletonList(secondAssetVersion)));\n        assertEquals(((Map) secondVerRestore.get(TEST_RESOURCE_PUBLIC_ID)).get(\"bytes\"), secondUpload.get(\"bytes\"));\n        Thread.sleep(SLEEP_TIMEOUT);\n        ApiResponse finalDeleteResp = api.deleteResources(Collections.singletonList(TEST_RESOURCE_PUBLIC_ID), ObjectUtils.emptyMap());\n        assertTrue(finalDeleteResp.containsKey(\"deleted\"));\n    }\n\n    @Test\n    public void testShouldRestoreTwoDifferentDeletedAssets() throws Exception {\n        final String PUBLIC_ID_BACKUP_1 = \"api_test_restore_versions_different_assets_1_\" + SUFFIX;\n        final String PUBLIC_ID_BACKUP_2 = \"api_test_restore_versions_different_assets_2_\" + SUFFIX;\n\n        final Uploader uploader = cloudinary.uploader();\n\n        Map firstUpload = uploader.upload(SRC_TEST_IMAGE,\n                ObjectUtils.asMap(\n                        \"public_id\", PUBLIC_ID_BACKUP_1,\n                        \"backup\", true,\n                        \"tags\", UPLOAD_TAGS\n                ));\n        Map secondUpload = uploader.upload(SRC_TEST_IMAGE,\n                ObjectUtils.asMap(\n                        \"public_id\", PUBLIC_ID_BACKUP_2,\n                        \"backup\", true,\n                        \"transformation\", new Transformation().angle(\"0\"),\n                        \"tags\", UPLOAD_TAGS\n                ));\n\n        ApiResponse deleteAll = api.deleteResources(Arrays.asList(PUBLIC_ID_BACKUP_1, PUBLIC_ID_BACKUP_2), ObjectUtils.emptyMap());\n        assertEquals(\"deleted\", ((Map) deleteAll.get(\"deleted\")).get(PUBLIC_ID_BACKUP_1));\n        assertEquals(\"deleted\", ((Map) deleteAll.get(\"deleted\")).get(PUBLIC_ID_BACKUP_2));\n\n        ApiResponse getFirstAssetVersion = api.resource(PUBLIC_ID_BACKUP_1, ObjectUtils.asMap(\"versions\", true));\n        ApiResponse getSecondAssetVersion = api.resource(PUBLIC_ID_BACKUP_2, ObjectUtils.asMap(\"versions\", true));\n\n        Object firstAssetVersion = ((List<Map>) getFirstAssetVersion.get(\"versions\")).get(0).get(\"version_id\");\n        Object secondAssetVersion = ((List<Map>) getSecondAssetVersion.get(\"versions\")).get(0).get(\"version_id\");\n\n        ApiResponse restore = api.restore(Arrays.asList(PUBLIC_ID_BACKUP_1, PUBLIC_ID_BACKUP_2),\n                ObjectUtils.asMap(\"versions\", Arrays.asList(firstAssetVersion, secondAssetVersion)));\n        assertEquals(((Map) restore.get(PUBLIC_ID_BACKUP_1)).get(\"bytes\"), firstUpload.get(\"bytes\"));\n        assertEquals(((Map) restore.get(PUBLIC_ID_BACKUP_2)).get(\"bytes\"), secondUpload.get(\"bytes\"));\n\n        ApiResponse finalDelete = api.deleteResources(Arrays.asList(PUBLIC_ID_BACKUP_1, PUBLIC_ID_BACKUP_2), ObjectUtils.emptyMap());\n        assertEquals(\"deleted\", ((Map) finalDelete.get(\"deleted\")).get(PUBLIC_ID_BACKUP_1));\n        assertEquals(\"deleted\", ((Map) finalDelete.get(\"deleted\")).get(PUBLIC_ID_BACKUP_2));\n    }\n\n    @Test\n    public void testEncodeUrlInApiCall() throws Exception {\n        String apiTestEncodeUrlInApiCall = \"sub^folder test\";\n        createdFolders.add(apiTestEncodeUrlInApiCall);\n        Map result = api.createFolder(apiTestEncodeUrlInApiCall, null);\n        assertEquals(\"sub^folder test\", result.get(\"path\"));\n    }\n\n    @Test\n    public void testUploadMapping() throws Exception {\n        String aptTestUploadMapping = \"api_test_upload_mapping\" + SUFFIX;\n        try {\n            api.deleteUploadMapping(aptTestUploadMapping, ObjectUtils.emptyMap());\n        } catch (Exception ignored) {\n\n        }\n        api.createUploadMapping(aptTestUploadMapping, ObjectUtils.asMap(\"template\", \"http://cloudinary.com\"));\n        Map result = api.uploadMapping(aptTestUploadMapping, ObjectUtils.emptyMap());\n        assertEquals(result.get(\"template\"), \"http://cloudinary.com\");\n        api.updateUploadMapping(aptTestUploadMapping, ObjectUtils.asMap(\"template\", \"http://res.cloudinary.com\"));\n        result = api.uploadMapping(aptTestUploadMapping, ObjectUtils.emptyMap());\n        assertEquals(result.get(\"template\"), \"http://res.cloudinary.com\");\n        result = api.uploadMappings(ObjectUtils.emptyMap());\n        ListIterator mappings = ((ArrayList) result.get(\"mappings\")).listIterator();\n        boolean found = false;\n        while (mappings.hasNext()) {\n            Map mapping = (Map) mappings.next();\n            if (mapping.get(\"folder\").equals(aptTestUploadMapping)\n                    && mapping.get(\"template\").equals(\"http://res.cloudinary.com\")) {\n                found = true;\n                break;\n            }\n        }\n        assertTrue(found);\n        api.deleteUploadMapping(aptTestUploadMapping, ObjectUtils.emptyMap());\n        result = api.uploadMappings(ObjectUtils.emptyMap());\n        found = false;\n        while (mappings.hasNext()) {\n            Map mapping = (Map) mappings.next();\n            if (mapping.get(\"folder\").equals(aptTestUploadMapping)\n                    && mapping.get(\"template\").equals(\"http://res.cloudinary.com\")) {\n                found = true;\n                break;\n            }\n        }\n        assertTrue(!found);\n    }\n\n    @Test\n    public void testPublishByIds() throws Exception {\n        Map response = cloudinary.uploader().upload(SRC_TEST_IMAGE, ObjectUtils.asMap(\"tags\", UPLOAD_TAGS, \"type\", \"authenticated\"));\n        String publicId = (String) response.get(\"public_id\");\n        response = cloudinary.api().publishByIds(Arrays.asList(publicId), null);\n        List published = (List) response.get(\"published\");\n        assertNotNull(published);\n        assertEquals(published.size(), 1);\n        Map resource = (Map) published.get(0);\n        assertEquals(resource.get(\"public_id\"), publicId);\n        assertNotNull(resource.get(\"url\"));\n        cloudinary.uploader().destroy(publicId, null);\n    }\n\n    @Test\n    public void testPublishWithType() throws Exception {\n        Map response = cloudinary.uploader().upload(SRC_TEST_IMAGE, ObjectUtils.asMap(\"tags\", UPLOAD_TAGS, \"type\", \"authenticated\"));\n        String publicId = (String) response.get(\"public_id\");\n\n        // publish with wrong type - verify publish fails\n        response = cloudinary.api().publishByIds(Arrays.asList(publicId), ObjectUtils.asMap(\"type\", \"private\"));\n        List published = (List) response.get(\"published\");\n        List failed = (List) response.get(\"failed\");\n        assertNotNull(published);\n        assertNotNull(failed);\n        assertEquals(published.size(), 0);\n        assertEquals(failed.size(), 1);\n\n        // publish with correct type - verify publish succeeds\n        response = cloudinary.api().publishByIds(Arrays.asList(publicId), ObjectUtils.asMap(\"type\", \"authenticated\"));\n        published = (List) response.get(\"published\");\n        failed = (List) response.get(\"failed\");\n        assertNotNull(published);\n        assertNotNull(failed);\n        assertEquals(published.size(), 1);\n        assertEquals(failed.size(), 0);\n\n        Map resource = (Map) published.get(0);\n        assertEquals(resource.get(\"public_id\"), publicId);\n        assertNotNull(resource.get(\"url\"));\n        cloudinary.uploader().destroy(publicId, null);\n    }\n\n    @Test\n    public void testPublishByPrefix() throws Exception {\n        Map response = cloudinary.uploader().upload(SRC_TEST_IMAGE, ObjectUtils.asMap(\"tags\", UPLOAD_TAGS, \"type\", \"authenticated\"));\n        String publicId = (String) response.get(\"public_id\");\n        response = cloudinary.api().publishByPrefix(publicId.substring(0, publicId.length() - 2), null);\n        List published = (List) response.get(\"published\");\n        assertNotNull(published);\n        assertEquals(published.size(), 1);\n        Map resource = (Map) published.get(0);\n        assertEquals(resource.get(\"public_id\"), publicId);\n        assertNotNull(resource.get(\"url\"));\n        cloudinary.uploader().destroy(publicId, null);\n    }\n\n    @Test\n    public void testPublishByTag() throws Exception {\n        Map response = cloudinary.uploader().upload(SRC_TEST_IMAGE, ObjectUtils.asMap(\"tags\", Arrays.asList(API_TAG, API_TAG + \"1\"), \"type\", \"authenticated\"));\n        String publicId = (String) response.get(\"public_id\");\n        response = cloudinary.api().publishByTag(API_TAG + \"1\", null);\n        List published = (List) response.get(\"published\");\n        assertNotNull(published);\n        assertEquals(published.size(), 1);\n        Map resource = (Map) published.get(0);\n        assertEquals(resource.get(\"public_id\"), publicId);\n        assertNotNull(resource.get(\"url\"));\n        cloudinary.uploader().destroy(publicId, null);\n    }\n\n    @Test\n    public void testUpdateResourcesAccessModeByIds() throws Exception {\n        Map response = cloudinary.uploader().upload(SRC_TEST_IMAGE, ObjectUtils.asMap(\"tags\", UPLOAD_TAGS, \"access_mode\", \"authenticated\"));\n        String publicId = (String) response.get(\"public_id\");\n        assertEquals(response.get(\"access_mode\"), \"authenticated\");\n        response = cloudinary.api().updateResourcesAccessModeByIds(\"public\", Arrays.asList(publicId), null);\n        List updated = (List) response.get(\"updated\");\n        assertNotNull(updated);\n        assertEquals(updated.size(), 1);\n        Map resource = (Map) updated.get(0);\n        assertEquals(resource.get(\"public_id\"), publicId);\n        assertEquals(resource.get(\"access_mode\"), \"public\");\n        cloudinary.uploader().destroy(publicId, null);\n    }\n\n    @Test\n    public void testUpdateResourcesAccessModeByPrefix() throws Exception {\n        Map response = cloudinary.uploader().upload(SRC_TEST_IMAGE, ObjectUtils.asMap(\"tags\", UPLOAD_TAGS, \"access_mode\", \"authenticated\"));\n        String publicId = (String) response.get(\"public_id\");\n        assertEquals(response.get(\"access_mode\"), \"authenticated\");\n        response = cloudinary.api().updateResourcesAccessModeByPrefix(\"public\", publicId.substring(0, publicId.length() - 2), null);\n        List updated = (List) response.get(\"updated\");\n        assertNotNull(updated);\n        assertEquals(updated.size(), 1);\n        Map resource = (Map) updated.get(0);\n        assertEquals(resource.get(\"public_id\"), publicId);\n        assertEquals(resource.get(\"access_mode\"), \"public\");\n        cloudinary.uploader().destroy(publicId, null);\n    }\n\n    @Test\n    public void testUpdateResourcesAccessModeByTag() throws Exception {\n        Map response = cloudinary.uploader().upload(SRC_TEST_IMAGE, ObjectUtils.asMap(\"tags\", Arrays.asList(API_TAG, API_TAG + \"2\"), \"access_mode\", \"authenticated\"));\n        String publicId = (String) response.get(\"public_id\");\n        assertEquals(response.get(\"access_mode\"), \"authenticated\");\n        response = cloudinary.api().updateResourcesAccessModeByTag(\"public\", API_TAG + \"2\", null);\n        List updated = (List) response.get(\"updated\");\n        assertNotNull(updated);\n        assertEquals(updated.size(), 1);\n        Map resource = (Map) updated.get(0);\n        assertEquals(resource.get(\"public_id\"), publicId);\n        assertEquals(resource.get(\"access_mode\"), \"public\");\n        cloudinary.uploader().destroy(publicId, null);\n    }\n\n    @Test\n    public void testQualityAnalysis() throws Exception {\n        ApiResponse result = cloudinary.api().resource(API_TEST, ObjectUtils.asMap(\"quality_analysis\", true));\n        assertNotNull(result.get(\"quality_analysis\"));\n    }\n\n    @Test(expected = NotFound.class)\n    public void testDeleteFolder() throws Exception {\n        String toDelete = \"todelete_\" + SUFFIX;\n        Map uploadResult = cloudinary.uploader().upload(SRC_TEST_IMAGE, asMap(\"tags\", UPLOAD_TAGS, \"folder\", toDelete));\n        Thread.sleep(SLEEP_TIMEOUT);\n        api.deleteResources(Collections.singletonList(uploadResult.get(\"public_id\").toString()), emptyMap());\n        ApiResponse result = api.deleteFolder(toDelete, emptyMap());\n        assertTrue(((ArrayList) result.get(\"deleted\")).contains(toDelete));\n\n        // should throw exception (folder not found):\n        api.deleteFolder(cloudinary.randomPublicId(), emptyMap());\n    }\n\n\n    @Test\n    public void testCinemagraphAnalysisResource() throws Exception {\n        ApiResponse res = api.resource(API_TEST, Collections.singletonMap(\"cinemagraph_analysis\", true));\n        assertNotNull(res.get(\"cinemagraph_analysis\"));\n    }\n\n    @Test\n    public void testAccessibilityAnalysisResource() throws Exception {\n        ApiResponse res = api.resource(API_TEST, Collections.singletonMap(\"accessibility_analysis\", true));\n        assertNotNull(res.get(\"accessibility_analysis\"));\n    }\n\n    @Test\n    public void testAnalyzeApi() throws Exception {\n        assumeAddonEnabled(\"captioning\");\n        ApiResponse res = api.analyze(\"uri\", \"captioning\", \"https://res.cloudinary.com/demo/image/upload/dog\", ObjectUtils.emptyMap());\n        assertNotNull(res);\n        assertNotNull(res.get(\"request_id\"));\n    }\n\n    @Test\n    public void testFolderDecoupling() {\n        //TODO: Need to build a unit testing infrastructure\n        Map params = new HashMap<String, Object>();\n        Map options = asMap(\n                \"asset_folder\", \"new_asset_folder\",\n                \"unique_display_name\", true);\n        Util.processWriteParameters(options, params);\n        assertEquals(\"new_asset_folder\", params.get(\"asset_folder\"));\n        assertEquals(true, params.get(\"unique_display_name\"));\n    }\n\n    @Test\n    public void testVisualSearch() {\n        //TODO: Need to build a unit testing infrastructure\n        Map params = new HashMap<String, Object>();\n        Map options = asMap(\n                \"visual_search\", true);\n        Util.processWriteParameters(options, params);\n        assertEquals(true, params.get(\"visual_search\"));\n    }\n\n    @Test\n    @Ignore(\"Skip test till FD is enabled for test accounts\")\n    public void testRenameFolder() throws Exception {\n        Map result = api.createFolder(\"apiTestCreateFolder\" + SUFFIX, null);\n        assertNotNull(result);\n\n        String folderName = (String) result.get(\"path\");\n        Map response = api.renameFolder(folderName, \"newFolderName\" + SUFFIX, ObjectUtils.emptyMap());\n        assertNotNull(response);\n    }\n\n    @Test\n    public void testDeleteBackedupAsset() throws Exception {\n        if (MockableTest.shouldTestFeature(Feature.BACKEDUP_ASSETS)) {\n            Map result = cloudinary.uploader().upload(SRC_TEST_IMAGE, ObjectUtils.asMap(\"backup\", true));\n\n            String publicId = (String) result.get(\"public_id\");\n            String assetId = (String) result.get(\"asset_id\");\n\n            ApiResponse getVersionsResp = api.resource(publicId, ObjectUtils.asMap(\"versions\", true));\n            List<Map> versions = (List<Map>) getVersionsResp.get(\"versions\");\n            String firstAssetVersion = (String) versions.get(0).get(\"version_id\");\n            ApiResponse response = api.deleteBackedUpAssets(assetId, new String[]{firstAssetVersion}, ObjectUtils.emptyMap());\n\n            assertNotNull(response);\n            assertEquals(response.get(\"asset_id\"), assetId);\n            List<String> deletedVersionIds = (List<String>) response.get(\"deleted_version_ids\");\n            assertEquals(deletedVersionIds.get(0), firstAssetVersion);\n        }\n    }\n\n    @Test\n    public void testAllowDerivedNextCursor() throws Exception {\n        String publicId = \"allowderivednextcursor_\" + SUFFIX;\n        Map options = ObjectUtils.asMap(\"public_id\", publicId, \"eager\", Arrays.asList(\n                new Transformation().width(100),\n                new Transformation().width(101),\n                new Transformation().width(102)\n        ));\n\n        try {\n            cloudinary.uploader().upload(SRC_TEST_IMAGE, options);\n            ApiResponse res = api.resource(publicId, Collections.singletonMap(\"max_results\", 1));\n            String derivedNextCursor = res.get(\"derived_next_cursor\").toString();\n            assertNotNull(derivedNextCursor);\n\n            ApiResponse res2 = api.resource(publicId, ObjectUtils.asMap(\"derived_next_cursor\", derivedNextCursor, \"max_results\", 1));\n            String derivedNextCursor2 = res2.get(\"derived_next_cursor\").toString();\n            assertNotNull(derivedNextCursor2);\n\n            assertNotEquals(derivedNextCursor, derivedNextCursor2);\n        } finally {\n            cloudinary.uploader().destroy(publicId, Collections.singletonMap(\"invalidate\", true));\n        }\n    }\n\n    @Test\n    public void testSignatureWithEscapingCharacters() {\n        String API_SIGN_REQUEST_CLOUD_NAME = \"dn6ot3ged\";\n        String API_SIGN_REQUEST_TEST_SECRET = \"hdcixPpR2iKERPwqvH6sHdK9cyac\";\n\n        Map<String, Object> paramsWithAmpersand = new HashMap<>();\n        paramsWithAmpersand.put(\"cloud_name\", API_SIGN_REQUEST_CLOUD_NAME);\n        paramsWithAmpersand.put(\"timestamp\", 1568810420);\n        paramsWithAmpersand.put(\"notification_url\", \"https://fake.com/callback?a=1&tags=hello,world\");\n\n        String signatureWithAmpersand = Util.produceSignature(paramsWithAmpersand, API_SIGN_REQUEST_TEST_SECRET, cloudinary.config.signatureVersion);\n\n        Map<String, Object> paramsSmuggled = new HashMap<>();\n        paramsSmuggled.put(\"cloud_name\", API_SIGN_REQUEST_CLOUD_NAME);\n        paramsSmuggled.put(\"timestamp\", 1568810420);\n        paramsSmuggled.put(\"notification_url\", \"https://fake.com/callback?a=1\");\n        paramsSmuggled.put(\"tags\", \"hello,world\");\n\n        String signatureSmuggled = Util.produceSignature(paramsSmuggled, API_SIGN_REQUEST_TEST_SECRET, cloudinary.config.signatureVersion);\n\n        assertNotEquals(signatureWithAmpersand, signatureSmuggled,\n                \"Signatures should be different to prevent parameter smuggling\");\n\n        String expectedSignature = \"4fdf465dd89451cc1ed8ec5b3e314e8a51695704\";\n        assertEquals(expectedSignature, signatureWithAmpersand);\n\n        String expectedSmuggledSignature = \"7b4e3a539ff1fa6e6700c41b3a2ee77586a025f9\";\n        assertEquals(expectedSmuggledSignature, signatureSmuggled);\n\n        String versionOneSignature = Util.produceSignature(paramsSmuggled, API_SIGN_REQUEST_TEST_SECRET, 1);\n\n        assertEquals(expectedSmuggledSignature, versionOneSignature);\n\n    }\n}\n"
  },
  {
    "path": "cloudinary-test-common/src/main/java/com/cloudinary/test/AbstractContextTest.java",
    "content": "package com.cloudinary.test;\n\nimport com.cloudinary.Cloudinary;\nimport com.cloudinary.Transformation;\nimport com.cloudinary.Uploader;\nimport com.cloudinary.utils.ObjectUtils;\nimport org.junit.*;\nimport org.junit.rules.TestName;\n\nimport java.io.IOException;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\nimport static com.cloudinary.utils.ObjectUtils.asMap;\nimport static org.hamcrest.Matchers.*;\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertThat;\nimport static org.junit.Assume.assumeNotNull;\n\n@SuppressWarnings({\"rawtypes\", \"unchecked\"})\nabstract public class AbstractContextTest extends MockableTest {\n\n    private static final String CONTEXT_TAG = \"context_tag_\" + String.valueOf(System.currentTimeMillis()) + SUFFIX;\n    public static final Map CONTEXT = asMap(\"caption\", \"some cäption\", \"alt\", \"alternativè\");\n    private Uploader uploader;\n\n    @BeforeClass\n    public static void setUpClass() throws Exception {\n        Cloudinary cloudinary = new Cloudinary();\n        if (cloudinary.config.apiSecret == null) {\n            System.err.println(\"Please setup environment for Upload test to run\");\n        }\n    }\n\n    private static Map uploadResource(String publicId) throws IOException {\n        return new Cloudinary().uploader().upload(SRC_TEST_IMAGE,\n                asMap( \"public_id\", publicId,\n                        \"tags\", new String[]{SDK_TEST_TAG, CONTEXT_TAG},\n                        \"context\", CONTEXT,\n                        \"transformation\", new Transformation().crop(\"scale\").width(10)));\n    }\n\n    @AfterClass\n    public static void tearDownClass() {\n        Cloudinary cloudinary = new Cloudinary();\n        try {\n            cloudinary.api().deleteResourcesByTag(CONTEXT_TAG, ObjectUtils.emptyMap());\n        } catch (Exception ignored) {\n        }\n    }\n\n    @Rule\n    public TestName currentTest = new TestName();\n\n    @Before\n    public void setUp() throws Exception {\n        System.out.println(\"Running \" + this.getClass().getName() + \".\" + currentTest.getMethodName());\n        cloudinary = new Cloudinary();\n        uploader = cloudinary.uploader();\n        assumeNotNull(cloudinary.config.apiSecret);\n\n    }\n\n    @Test\n    public void testExplicit() throws Exception {\n        String publicId = \"explicit_id\" + SUFFIX;\n        uploadResource(publicId);\n        //should allow sending context\n        Map differentContext = asMap(\"caption\", \"different = caption\", \"alt2\", \"alt|alternative alternative\");\n        Map result = uploader.explicit(publicId, asMap(\"type\", \"upload\", \"context\", differentContext));\n        assertEquals(\"explicit API should return the new context\", asMap(\"custom\", differentContext), result.get(\"context\"));\n        Map resource = cloudinary.api().resource(publicId, asMap(\"context\", true));\n        assertEquals(\"explicit API should replace the context\", asMap(\"custom\", differentContext), resource.get(\"context\"));\n    }\n\n    @Test\n    public void testAddContext() throws Exception {\n        String publicId = \"add_context_id\" + SUFFIX;\n        Map resource = uploadResource(publicId);\n        Map context = new HashMap((Map)((Map)resource.get(\"context\")).get(\"custom\"));\n        context.put(\"caption\", \"new caption\");\n        Map result = uploader.addContext(asMap(\"caption\", \"new caption\"), new String[]{publicId, \"no-such-id\"}, null);\n        assertThat(\"addContext should return a list of modified public IDs\", (List<String>) result.get(\"public_ids\"), contains(publicId));\n\n        resource = cloudinary.api().resource(publicId, asMap(\"context\", true));\n        assertEquals(asMap(\"custom\", context), resource.get(\"context\"));\n    }\n\n    @Test\n    public void testRemoveAllContext() throws Exception {\n        String publicId = \"remove_context_id\" + SUFFIX;\n        uploadResource(publicId);\n        Map result = uploader.removeAllContext(new String[]{publicId, \"no-such-id\"}, null);\n        assertThat((List<String>) result.get(\"public_ids\"), contains(publicId));\n\n        Map resource = cloudinary.api().resource(publicId, asMap(\"context\", true));\n        assertThat((Map<? extends String, ?>)resource, not(hasKey(\"context\")));\n    }\n}\n"
  },
  {
    "path": "cloudinary-test-common/src/main/java/com/cloudinary/test/AbstractFoldersApiTest.java",
    "content": "package com.cloudinary.test;\n\nimport com.cloudinary.Api;\nimport com.cloudinary.Cloudinary;\nimport com.cloudinary.api.ApiResponse;\nimport com.cloudinary.utils.ObjectUtils;\nimport org.junit.Before;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.rules.TestName;\n\nimport java.util.List;\n\nimport static org.junit.Assert.*;\nimport static org.junit.Assume.assumeNotNull;\n\n@SuppressWarnings({\"rawtypes\"})\nabstract public class AbstractFoldersApiTest extends MockableTest {\n    protected Api api;\n\n    @Rule\n    public TestName currentTest = new TestName();\n\n    @Before\n    public void setUp() {\n        System.out.println(\"Running \" + this.getClass().getName() + \".\" + currentTest.getMethodName());\n        this.cloudinary = new Cloudinary();\n        assumeNotNull(cloudinary.config.apiSecret);\n        this.api = cloudinary.api();\n    }\n\n    @Test\n    public void testRootFolderWithParams() throws Exception {\n        String rootFolder1Name = \"rootFolderWithParamsTest1\" + SUFFIX;\n        assertTrue((Boolean) api.createFolder(rootFolder1Name, null).get(\"success\"));\n\n        String rootFolder2Name = \"rootFolderWithParamsTest2\" + SUFFIX;\n        assertTrue((Boolean) api.createFolder(rootFolder2Name, null).get(\"success\"));\n\n        Thread.sleep(2000);\n\n        ApiResponse rootResponse1 = api.rootFolders(ObjectUtils.asMap(\"max_results\", 1));\n        List rootFolders1 = (List) rootResponse1.get(\"folders\");\n        assertNotNull(rootFolders1);\n        assertEquals(1, rootFolders1.size());\n\n        String nextCursor = (String) rootResponse1.get(\"next_cursor\");\n        assertNotNull(nextCursor);\n\n        ApiResponse rootResponse2 = api.rootFolders(ObjectUtils.asMap(\"max_results\", 1, \"next_cursor\", nextCursor));\n        List folders2 = (List) rootResponse2.get(\"folders\");\n        assertNotNull(folders2);\n        assertEquals(1, folders2.size());\n\n        assertTrue(((List) api.deleteFolder(rootFolder1Name, null).get(\"deleted\")).contains(rootFolder1Name));\n        assertTrue(((List) api.deleteFolder(rootFolder2Name, null).get(\"deleted\")).contains(rootFolder2Name));\n    }\n\n    @Test\n    public void testSubFolderWithParams() throws Exception {\n        String rootFolderName = \"subfolderWithParamsTest\" + SUFFIX;\n        assertTrue((Boolean) api.createFolder(rootFolderName, null).get(\"success\"));\n\n        String subFolder1Name = rootFolderName + \"/subfolder1\" + SUFFIX;\n        assertTrue((Boolean) api.createFolder(subFolder1Name, null).get(\"success\"));\n\n        String subFolder2Name = rootFolderName + \"/subfolder2\" + SUFFIX;\n        assertTrue((Boolean) api.createFolder(subFolder2Name, null).get(\"success\"));\n\n        Thread.sleep(2000);\n\n        ApiResponse response = api.subFolders(rootFolderName, ObjectUtils.asMap(\"max_results\", 1));\n        List folders = (List) response.get(\"folders\");\n        assertNotNull(folders);\n        assertEquals(1, folders.size());\n\n        String nextCursor = (String) response.get(\"next_cursor\");\n        assertNotNull(nextCursor);\n\n        ApiResponse response2 = api.subFolders(rootFolderName, ObjectUtils.asMap(\"max_results\", 1, \"next_cursor\", nextCursor));\n        List folders2 = (List) response2.get(\"folders\");\n        assertNotNull(folders2);\n        assertEquals(1, folders2.size());\n\n        ApiResponse result = api.deleteFolder(rootFolderName, null);\n        assertTrue(((List) result.get(\"deleted\")).contains(rootFolderName));\n    }\n\n    @Test\n    public void testDeleteFolderWithSkipBackup() throws Exception {\n        //Create\n        String rootFolderName = \"deleteFolderWithSkipBackup\" + SUFFIX;\n        assertTrue((Boolean) api.createFolder(rootFolderName, null).get(\"success\"));\n\n        //Delete\n        ApiResponse result = api.deleteFolder(rootFolderName, ObjectUtils.asMap(\"skip_backup\", \"true\"));\n        assertTrue(((List) result.get(\"deleted\")).contains(rootFolderName));\n\n\n    }\n}\n"
  },
  {
    "path": "cloudinary-test-common/src/main/java/com/cloudinary/test/AbstractSearchTest.java",
    "content": "package com.cloudinary.test;\n\nimport com.cloudinary.Cloudinary;\nimport com.cloudinary.Search;\nimport com.cloudinary.utils.ObjectUtils;\nimport org.junit.*;\nimport org.junit.rules.TestName;\n\nimport java.lang.reflect.Field;\nimport java.util.*;\n\nimport static org.hamcrest.Matchers.hasEntry;\nimport static org.hamcrest.Matchers.hasItem;\nimport static org.junit.Assert.*;\nimport static org.junit.Assume.assumeNotNull;\n\n@SuppressWarnings({\"rawtypes\", \"unchecked\", \"JavaDoc\"})\nabstract public class AbstractSearchTest extends MockableTest {\n    @Rule\n    public TestName currentTest = new TestName();\n    private static final String SEARCH_TAG = \"search_test_tag_\" + SUFFIX;\n    public static final String[] UPLOAD_TAGS = {SDK_TEST_TAG, SEARCH_TAG};\n    private static final String SEARCH_TEST = \"search_test_\" + SUFFIX;\n    private static final String SEARCH_FOLDER = \"search_folder_\" + SUFFIX;\n    private static final String SEARCH_TEST_1 = SEARCH_TEST + \"_1\";\n    private static final String SEARCH_TEST_2 = SEARCH_TEST + \"_2\";\n    private static String SEARCH_TEST_ASSET_ID_1;\n\n    @BeforeClass\n    public static void setUpClass() throws Exception {\n        Cloudinary cloudinary = new Cloudinary();\n        Map options = ObjectUtils.asMap(\"public_id\", SEARCH_TEST, \"tags\", UPLOAD_TAGS, \"context\", \"stage=in_review\");\n        cloudinary.api().deleteResourcesByTag(SEARCH_TAG, null);\n        cloudinary.uploader().upload(SRC_TEST_IMAGE, options);\n        options = ObjectUtils.asMap(\"public_id\", SEARCH_TEST_1, \"tags\", UPLOAD_TAGS, \"context\", \"stage=new\");\n        SEARCH_TEST_ASSET_ID_1 = cloudinary.uploader().upload(SRC_TEST_IMAGE, options).get(\"asset_id\").toString();\n        options = ObjectUtils.asMap(\"public_id\", SEARCH_TEST_2, \"tags\", UPLOAD_TAGS, \"context\", \"stage=validated\");\n        cloudinary.uploader().upload(SRC_TEST_IMAGE, options);\n        try {\n            Thread.sleep(5000); //wait for search indexing\n        } catch (InterruptedException e) {\n            e.printStackTrace();\n        }\n    }\n\n    @AfterClass\n    public static void tearDownClass() throws Exception {\n        Cloudinary cloudinary = new Cloudinary();\n        cloudinary.api().deleteResourcesByTag(SEARCH_TAG, null);\n        try {\n            cloudinary.api().deleteFolder(SEARCH_FOLDER, null);\n        } catch (Exception e){\n            System.err.println(e.getMessage());\n        }\n    }\n\n    @Before\n    public void setUp() {\n        System.out.println(\"Running \" + this.getClass().getName() + \".\" + currentTest.getMethodName());\n        this.cloudinary = new Cloudinary();\n        assumeNotNull(cloudinary.config.apiSecret);\n    }\n\n    @Test\n    public void shouldFindResourcesByTag() throws Exception {\n        Map result = cloudinary.search().expression(String.format(\"tags:%s\", SEARCH_TAG)).execute();\n        List<Map> resources = (List<Map>) result.get(\"resources\");\n        assertEquals(3, resources.size());\n    }\n\n    @Test\n    public void shouldFindFolders() throws Exception {\n        Map createFolderResult = cloudinary.api().createFolder(SEARCH_FOLDER, null);\n        Thread.sleep(3000);\n        if ((Boolean) createFolderResult.get(\"success\")) {\n            Map result = cloudinary.searchFolders().expression(String.format(\"name:%s\", SEARCH_FOLDER)).execute();\n            System.out.println(\"SUCCESS!\");\n            final List<Map> folders = (List) result.get(\"folders\");\n            assertThat(folders, hasItem(hasEntry(\"name\", SEARCH_FOLDER)));\n        }\n    }\n\n    @Test\n    public void shouldFindResourceByPublicId() throws Exception {\n        Map result = cloudinary.search().expression(String.format(\"public_id:%s\", SEARCH_TEST_1)).execute();\n        List<Map> resources = (List<Map>) result.get(\"resources\");\n        assertEquals(1, resources.size());\n    }\n\n    @Test\n    public void shouldFindResourceByAssetId() throws Exception {\n        Map result = cloudinary.search().expression(String.format(\"asset_id:%s\", SEARCH_TEST_ASSET_ID_1)).execute();\n        List<Map> resources = (List<Map>) result.get(\"resources\");\n        assertEquals(1, resources.size());\n    }\n\n    @Test\n    public void testShouldNotDuplicateValues() throws Exception {\n        Search request = cloudinary.search().maxResults(1).\n                sortBy(\"created_at\", \"asc\")\n                .sortBy(\"created_at\", \"desc\")\n                .sortBy(\"public_id\", \"asc\")\n                .aggregate(\"format\")\n                .aggregate(\"format\")\n                .aggregate(\"resource_type\")\n                .withField(\"context\")\n                .withField(\"context\")\n                .withField(\"tags\");\n        Field[] fields = Search.class.getDeclaredFields();\n        for(Field field : fields) {\n            if(field.getName() == \"aggregateParam\") {\n                field.setAccessible(true);\n                ArrayList<String> aggregateList = (ArrayList<String>) field.get(request);\n                Set<String> testSet = new HashSet<String>(aggregateList);\n                assertTrue(aggregateList.size() == testSet.size());\n            }\n            if (field.getName() == \"withFieldParam\") {\n                field.setAccessible(true);\n                ArrayList<String> withFieldList = (ArrayList<String>) field.get(request);\n                Set<String> testSet = new HashSet<String>(withFieldList);\n                assertTrue(withFieldList.size() == testSet.size());\n            }\n            if (field.getName() == \"sortByParam\") {\n                field.setAccessible(true);\n                ArrayList<HashMap<String, Object>> sortByList = (ArrayList<HashMap<String, Object>>) field.get(request);\n                Set<HashMap<String, Object>> testSet = new HashSet<HashMap<String, Object>>(sortByList);\n                assertTrue(sortByList.size() == testSet.size());\n            }\n        }\n    }\n\n    @Test\n    public void shouldPaginateResourcesLimitedByTagAndOrderdByAscendingPublicId() throws Exception {\n        List<Map> resources;\n        Map result = cloudinary.search().maxResults(1).expression(String.format(\"tags:%s\", SEARCH_TAG)).sortBy(\"public_id\", \"asc\").execute();\n        resources = (List<Map>) result.get(\"resources\");\n        assertEquals(1, resources.size());\n        assertEquals(3, result.get(\"total_count\"));\n        assertEquals(SEARCH_TEST, resources.get(0).get(\"public_id\"));\n\n\n        result = cloudinary.search().maxResults(1).expression(String.format(\"tags:%s\", SEARCH_TAG)).sortBy(\"public_id\", \"asc\")\n                .nextCursor(ObjectUtils.asString(result.get(\"next_cursor\"))).execute();\n        resources = (List<Map>) result.get(\"resources\");\n\n        assertEquals(1, resources.size());\n        assertEquals(3, result.get(\"total_count\"));\n        assertEquals(SEARCH_TEST_1, resources.get(0).get(\"public_id\"));\n\n        result = cloudinary.search().maxResults(1).expression(String.format(\"tags:%s\", SEARCH_TAG)).sortBy(\"public_id\", \"asc\")\n                .nextCursor(ObjectUtils.asString(result.get(\"next_cursor\"))).execute();\n        resources = (List<Map>) result.get(\"resources\");\n\n        assertEquals(1, resources.size());\n        assertEquals(3, result.get(\"total_count\"));\n        assertEquals(SEARCH_TEST_2, resources.get(0).get(\"public_id\"));\n        assertNull(result.get(\"next_cursor\"));\n    }\n\n    @Test\n    public void testShouldBuildSearchUrl() throws Exception {\n        String nextCursor = \"db27cfb02b3f69cb39049969c23ca430c6d33d5a3a7c3ad1d870c54e1a54ee0faa5acdd9f6d288666986001711759d10\";\n        Cloudinary cloudinaryToSearch = new Cloudinary(\"cloudinary://key:secret@test123\");\n        cloudinaryToSearch.config.secure = true;\n\n        Search search = cloudinaryToSearch.search().expression(\"resource_type:image AND tags=kitten AND uploaded_at>1d AND bytes>1m\").sortBy(\"public_id\", \"desc\").maxResults(30);\n        String base64Query = \"eyJleHByZXNzaW9uIjoicmVzb3VyY2VfdHlwZTppbWFnZSBBTkQgdGFncz1raXR0ZW4gQU5EIHVwbG9hZGVkX2F0PjFkIEFORCBieXRlcz4xbSIsIm1heF9yZXN1bHRzIjozMCwic29ydF9ieSI6W3sicHVibGljX2lkIjoiZGVzYyJ9XX0=\";\n        String ttl300Signature = \"431454b74cefa342e2f03e2d589b2e901babb8db6e6b149abf25bc0dd7ab20b7\";\n        String ttl1000Signature = \"25b91426a37d4f633a9b34383c63889ff8952e7ffecef29a17d600eeb3db0db7\";\n\n        assertEquals(String.format(\"https://res.cloudinary.com/%s/search/%s/%d/%s\", cloudinaryToSearch.config.cloudName, ttl300Signature, 300, base64Query), search.toUrl());\n        assertEquals(String.format(\"https://res.cloudinary.com/%s/search/%s/%d/%s/%s\", cloudinaryToSearch.config.cloudName, ttl300Signature, 300, base64Query, nextCursor), search.toUrl(nextCursor));\n        assertEquals(String.format(\"https://res.cloudinary.com/%s/search/%s/%d/%s/%s\", cloudinaryToSearch.config.cloudName, ttl1000Signature, 1000, base64Query, nextCursor), search.toUrl(1000, nextCursor));\n        cloudinaryToSearch.config.privateCdn = true;\n        assertEquals(String.format(\"https://%s-res.cloudinary.com/search/%s/%d/%s\", cloudinaryToSearch.config.cloudName, ttl300Signature, 300, base64Query), search.toUrl(300, \"\"));\n    }\n\n    @Test\n    public void testSearchWithSelectiveResponse() throws Exception {\n        Map result = cloudinary.search().expression(String.format(\"tags:%s\", SEARCH_TAG)).fields(\"width\").fields(\"height\").execute();\n        List<Map> resources = (List<Map>) result.get(\"resources\");\n        assertEquals(3, resources.size());\n        Map resource = resources.get(0);\n        assertNotNull(resource);\n        assertNotNull(resource.get(\"width\"));\n        assertNotNull(resource.get(\"height\"));\n        assertNull(resource.get(\"format\"));\n    }\n}"
  },
  {
    "path": "cloudinary-test-common/src/main/java/com/cloudinary/test/AbstractStreamingProfilesApiTest.java",
    "content": "package com.cloudinary.test;\n\nimport com.cloudinary.Api;\nimport com.cloudinary.Cloudinary;\nimport com.cloudinary.Transformation;\nimport com.cloudinary.api.ApiResponse;\nimport com.cloudinary.api.exceptions.AlreadyExists;\nimport com.cloudinary.api.exceptions.NotFound;\nimport com.cloudinary.utils.ObjectUtils;\nimport org.hamcrest.Matcher;\nimport org.hamcrest.Matchers;\nimport org.junit.*;\nimport org.junit.rules.TestName;\n\nimport java.io.IOException;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Map;\n\nimport static org.hamcrest.Matchers.*;\nimport static org.junit.Assert.*;\nimport static org.junit.Assume.assumeNotNull;\n\nabstract public class AbstractStreamingProfilesApiTest extends MockableTest {\n    private static final String PROFILE_NAME = \"api_test_streaming_profile\" + SUFFIX;\n    protected Api api;\n    private static final List<String> PREDEFINED_PROFILES = Arrays.asList(\"4k\", \"full_hd\", \"hd\", \"sd\", \"full_hd_wifi\", \"full_hd_lean\", \"hd_lean\");\n    public static final String UPDATE_PROFILE_NAME = PROFILE_NAME + \"_update\";\n    public static final String DELETE_PROFILE_NAME = PROFILE_NAME + \"_delete\";\n    public static final String CREATE_PROFILE_NAME = PROFILE_NAME + \"_create\";\n\n    @BeforeClass\n    public static void setUpClass() throws IOException {\n        Cloudinary cloudinary = new Cloudinary();\n        if (cloudinary.config.apiSecret == null) {\n            System.err.println(\"Please setup environment for Upload test to run\");\n        }\n    }\n\n    @Rule\n    public TestName currentTest = new TestName();\n\n    @Before\n    public void setUp() {\n        System.out.println(\"Running \" + this.getClass().getName() + \".\" + currentTest.getMethodName());\n        this.cloudinary = new Cloudinary();\n        assumeNotNull(cloudinary.config.apiSecret);\n        this.api = cloudinary.api();\n    }\n\n    @Test\n    public void testCreate() throws Exception {\n        ApiResponse result = api.createStreamingProfile(CREATE_PROFILE_NAME, null, Collections.singletonList(ObjectUtils.asMap(\n                \"transformation\", new Transformation().crop(\"limit\").width(1200).height(1200).bitRate(\"5m\")\n        )), ObjectUtils.emptyMap());\n\n        assertTrue(result.containsKey(\"data\"));\n        Map profile = (Map) result.get(\"data\");\n        assertThat(profile, (Matcher) hasEntry(\"name\", (Object) CREATE_PROFILE_NAME));\n    }\n\n    @Test\n    public void testGet() throws Exception {\n        ApiResponse result = api.getStreamingProfile(PREDEFINED_PROFILES.get(0));\n        assertTrue(result.containsKey(\"data\"));\n        Map profile = (Map) result.get(\"data\");\n        assertThat(profile, (Matcher) hasEntry(\"name\", (Object) (PREDEFINED_PROFILES.get(0))));\n\n    }\n\n    @Test\n    public void testList() throws Exception {\n        ApiResponse result = api.listStreamingProfiles();\n        assertTrue(result.containsKey(\"data\"));\n        List profiles = (List) result.get(\"data\");\n        // check that the list contains all predefined profiles\n        for (String p :\n                PREDEFINED_PROFILES) {\n            assertThat(profiles, (Matcher) hasItem(hasEntry(\"name\", p)));\n        }\n    }\n\n    @Test\n    public void testDelete() throws Exception {\n        ApiResponse result;\n        try {\n            api.createStreamingProfile(DELETE_PROFILE_NAME, null, Collections.singletonList(ObjectUtils.asMap(\n                    \"transformation\", new Transformation().crop(\"limit\").width(1200).height(1200).bitRate(\"5m\")\n            )), ObjectUtils.emptyMap());\n        } catch (AlreadyExists ignored) {\n        }\n\n        result = api.deleteStreamingProfile(DELETE_PROFILE_NAME);\n        assertEquals(\"deleted\", result.get(\"message\"));\n    }\n\n    @Test\n    public void testUpdate() throws Exception {\n        try {\n            api.createStreamingProfile(UPDATE_PROFILE_NAME, null, Collections.singletonList(ObjectUtils.asMap(\n                    \"transformation\", new Transformation().crop(\"limit\").width(1200).height(1200).bitRate(\"5m\")\n            )), ObjectUtils.emptyMap());\n        } catch (AlreadyExists ignored) {\n        }\n        Map result = api.updateStreamingProfile(UPDATE_PROFILE_NAME, null, Collections.singletonList(\n                ObjectUtils.asMap(\"transformation\",\n                        new Transformation().crop(\"limit\").width(800).height(800).bitRate(\"5m\")\n                )), ObjectUtils.emptyMap());\n\n        assertTrue(result.containsKey(\"data\"));\n        assertThat(result, (Matcher) hasEntry(\"message\", (Object) \"updated\"));\n        Map profile = (Map) result.get(\"data\");\n        assertThat(profile, (Matcher) hasEntry(\"name\", (Object) UPDATE_PROFILE_NAME));\n        assertThat(profile, Matchers.hasEntry(equalTo(\"representations\"), (Matcher) hasItem(hasKey(\"transformation\"))));\n        final Map representation = (Map) ((List) profile.get(\"representations\")).get(0);\n        Map transformation = (Map) ((List) representation.get(\"transformation\")).get(0);\n        assertThat(transformation, allOf(\n                (Matcher) hasEntry(\"width\", 800),\n                (Matcher) hasEntry(\"height\", 800),\n                (Matcher) hasEntry(\"crop\", \"limit\"),\n                (Matcher) hasEntry(\"bit_rate\", \"5m\")\n        ));\n    }\n\n    @AfterClass\n    public static void tearDownClass() throws Exception {\n        Api api = new Cloudinary().api();\n        try {\n            api.deleteStreamingProfile(CREATE_PROFILE_NAME);\n        } catch (NotFound ignored) {\n        }\n\n        try {\n            api.deleteStreamingProfile(UPDATE_PROFILE_NAME);\n        } catch (NotFound ignored) {\n        }\n\n        try {\n            // this should already be gone but in case that deletion-test failed we still need to cleanup the account.\n            api.deleteStreamingProfile(DELETE_PROFILE_NAME);\n        } catch (NotFound ignored) {\n        }\n    }\n}\n"
  },
  {
    "path": "cloudinary-test-common/src/main/java/com/cloudinary/test/AbstractStructuredMetadataTest.java",
    "content": "package com.cloudinary.test;\n\nimport com.cloudinary.Api;\nimport com.cloudinary.Cloudinary;\nimport com.cloudinary.api.ApiResponse;\nimport com.cloudinary.api.exceptions.BadRequest;\nimport com.cloudinary.metadata.*;\n\nimport com.cloudinary.test.helpers.Feature;\nimport com.cloudinary.utils.ObjectUtils;\nimport org.hamcrest.Matchers;\nimport org.junit.*;\nimport org.junit.rules.TestName;\n\nimport java.io.IOException;\nimport java.util.*;\n\nimport static com.cloudinary.utils.ObjectUtils.asMap;\nimport static org.junit.Assert.*;\nimport static org.junit.Assume.assumeNotNull;\n\npublic abstract class AbstractStructuredMetadataTest extends MockableTest {\n    private static final String METADATA_UPLOADER_TAG = SDK_TEST_TAG + \"_uploader\";\n    private static final String PUBLIC_ID = \"before_class_public_id\" + SUFFIX;\n    private static final String PRIVATE_PUBLIC_ID = \"before_class_private_public_id\" + SUFFIX;\n    protected Api api;\n    public static final List<String> metadataFieldExternalIds = new ArrayList<String>();\n\n    @BeforeClass\n    public static void setUpClass() throws IOException {\n        Cloudinary cloudinary = new Cloudinary();\n        if (cloudinary.config.apiSecret == null) {\n            System.err.println(\"Please setup environment for Upload test to run\");\n        }\n\n        cloudinary.uploader().upload(SRC_TEST_IMAGE, asMap(\"public_id\", PUBLIC_ID));\n        cloudinary.uploader().upload(SRC_TEST_IMAGE, asMap(\"public_id\", PRIVATE_PUBLIC_ID, \"type\", \"private\"));\n    }\n\n    @AfterClass\n    public static void tearDownClass() throws Exception {\n        Api api = new Cloudinary().api();\n\n        for (String externalId : metadataFieldExternalIds) {\n            try {\n                api.deleteMetadataField(externalId);\n            } catch (Exception ignored) {\n            }\n        }\n    }\n\n    @Rule\n    public TestName currentTest = new TestName();\n\n    @Before\n    public void setUp() {\n        System.out.println(\"Running \" + this.getClass().getName() + \".\" + currentTest.getMethodName());\n        this.cloudinary = new Cloudinary();\n        assumeNotNull(cloudinary.config.apiSecret);\n        this.api = cloudinary.api();\n    }\n\n    @Test\n    public void testCreateMetadata() throws Exception {\n        StringMetadataField stringField = newFieldInstance(\"testCreateMetadata_1\", true);\n        ApiResponse result = addFieldToAccount(stringField);\n        assertNotNull(result);\n        assertEquals(stringField.getLabel(), result.get(\"label\"));\n\n        SetMetadataField setField = createSetField(\"testCreateMetadata_2\");\n        result = cloudinary.api().addMetadataField(setField);\n        assertNotNull(result);\n        assertEquals(setField.getLabel(), result.get(\"label\"));\n    }\n\n    @Test\n    public void testCreateSetMetadataWithAllowDynamicListValues() throws Exception {\n        SetMetadataField setField = createSetField(\"testCreateMetadata_4\");\n        ApiResponse result = cloudinary.api().addMetadataField(setField);\n        assertNotNull(result);\n        assertEquals(setField.getLabel(), result.get(\"label\"));\n        assertEquals(true, result.get(\"allow_dynamic_list_values\"));\n    }\n\n    @Test\n    public void testFieldRestrictions() throws Exception {\n        StringMetadataField stringField = newFieldInstance(\"testCreateMetadata_3\", true);\n        stringField.setRestrictions(new Restrictions().setReadOnlyUI());\n\n        ApiResponse result = api.addMetadataField(stringField);\n        assertNotNull(result);\n        Map<String, Object> restrictions = (Map<String, Object>) result.get(\"restrictions\");\n        assertNotNull(restrictions);\n        assertTrue((Boolean) restrictions.get(\"readonly_ui\"));\n    }\n\n    @Test\n    public void testDateFieldDefaultValueValidation() throws Exception {\n        // now minus 3 days hours.\n        Date max = new Date();\n        Date min = new Date(max.getTime() - 72 * 60 * 60 * 1000);\n\n        Date legalValue = new Date(min.getTime() + 36 * 60 * 60 * 1000);\n        Date illegalValue = new Date(max.getTime() + 36 * 60 * 60 * 1000);\n\n        DateMetadataField dateMetadataField = new DateMetadataField();\n        dateMetadataField.setLabel(\"Start date\" + new Date().getTime());\n\n        List<MetadataValidation> rules = new ArrayList<MetadataValidation>();\n        rules.add(new MetadataValidation.DateGreaterThan(min));\n        rules.add(new MetadataValidation.DateLessThan(max));\n        dateMetadataField.setValidation(new MetadataValidation.AndValidator(rules));\n\n        String message = null;\n        ApiResponse res = null;\n        try {\n            // should fail\n            dateMetadataField.setDefaultValue(illegalValue);\n            res = api.addMetadataField(dateMetadataField);\n            // this line should not be reached if all is working well, but when it's not we still want to clean it up:\n            metadataFieldExternalIds.add(res.get(\"external_id\").toString());\n        } catch (BadRequest e) {\n            message = e.getMessage();\n        }\n\n        assertEquals(message, \"default_value is invalid\");\n\n        // should work:\n        dateMetadataField.setDefaultValue(legalValue);\n        res = api.addMetadataField(dateMetadataField);\n        metadataFieldExternalIds.add(res.get(\"external_id\").toString());\n    }\n\n    @Test\n    public void testListFields() throws Exception {\n        StringMetadataField stringField = newFieldInstance(\"testListFields\", true);\n        addFieldToAccount(stringField);\n\n        ApiResponse result = cloudinary.api().listMetadataFields();\n        assertNotNull(result);\n        assertNotNull(result.get(\"metadata_fields\"));\n        assertTrue(((List)result.get(\"metadata_fields\")).size() > 0);\n    }\n\n    @Test\n    public void testGetMetadata() throws Exception {\n        ApiResponse fieldResult = addFieldToAccount(newFieldInstance(\"testGetMetadata\", true));\n        ApiResponse result = api.metadataFieldByFieldId(fieldResult.get(\"external_id\").toString());\n        assertNotNull(result);\n        assertEquals(fieldResult.get(\"label\"), result.get(\"label\"));\n    }\n\n    @Test\n    public void testUpdateField() throws Exception {\n        StringMetadataField metadataField = newFieldInstance(\"testUpdateField\", false);\n        ApiResponse fieldResult = addFieldToAccount(metadataField);\n        assertNotEquals(\"new_def\", fieldResult.get(\"default_value\"));\n        metadataField.setDefaultValue(\"new_def\");\n        metadataField.setDefaultDisabled(true);\n        metadataField.setRestrictions(new Restrictions().setReadOnlyUI());\n        ApiResponse result = api.updateMetadataField(fieldResult.get(\"external_id\").toString(), metadataField);\n        assertNotNull(result);\n        assertEquals(\"new_def\", result.get(\"default_value\"));\n        assertEquals(true, result.get(\"default_disabled\"));\n        Map<String, Object> restrictions = (Map<String, Object>) result.get(\"restrictions\");\n        assertNotNull(restrictions);\n        assertTrue((Boolean)restrictions.get(\"readonly_ui\"));\n    }\n\n    @Test\n    public void testDeleteField() throws Exception {\n        ApiResponse fieldResult = addFieldToAccount(newFieldInstance(\"testDeleteField\", true));\n        ApiResponse result = api.deleteMetadataField(fieldResult.get(\"external_id\").toString());\n        assertNotNull(result);\n        assertEquals(\"ok\", result.get(\"message\"));\n    }\n\n    @Test\n    public void testUpdateDatasource() throws Exception {\n        SetMetadataField setField = createSetField(\"testUpdateDatasource\");\n        ApiResponse fieldResult = addFieldToAccount(setField);\n        MetadataDataSource.Entry newEntry = new MetadataDataSource.Entry(\"id1\", \"new1\");\n        ApiResponse result = api.updateMetadataFieldDatasource(fieldResult.get(\"external_id\").toString(), Collections.singletonList(newEntry));\n        assertNotNull(result);\n        assertEquals(\"new1\", ((Map) ((List) result.get(\"values\")).get(0)).get(\"value\"));\n    }\n\n    @Test\n    public void testDeleteDatasourceEntries() throws Exception {\n        SetMetadataField setField = createSetField(\"testDeleteDatasourceEntries\");\n        ApiResponse fieldResult = addFieldToAccount(setField);\n        ApiResponse result = api.deleteDatasourceEntries(fieldResult.get(\"external_id\").toString(), Collections.singletonList(\"id1\"));\n        assertNotNull(result);\n    }\n\n    @Test\n    public void testRestoreDatasourceEntries() throws Exception {\n        SetMetadataField setField = createSetField(\"testRestoreDatasourceEntries\");\n        ApiResponse fieldResult = addFieldToAccount(setField);\n        String fieldExternalId = fieldResult.get(\"external_id\").toString();\n        api.deleteDatasourceEntries(fieldExternalId, Collections.singletonList(\"id1\"));\n        ApiResponse result = api.restoreDatasourceEntries(fieldExternalId, Collections.singletonList(\"id1\"));\n        assertNotNull(result);\n    }\n\n    @Test\n    public void testReorderMetadataFieldsByLabel() throws Exception {\n        AddStringField(\"some_value\");\n        AddStringField(\"aaa\");\n        AddStringField(\"zzz\");\n\n        ApiResponse result = api.reorderMetadataFields(\"label\", null, Collections.EMPTY_MAP);\n        assertThat(getField(result, 0), Matchers.containsString(\"aaa\"));\n\n        result = api.reorderMetadataFields(\"label\", \"desc\", Collections.EMPTY_MAP);\n        assertThat(getField(result, 0), Matchers.containsString(\"zzz\"));\n\n        result = api.reorderMetadataFields(\"label\", \"asc\", Collections.EMPTY_MAP);\n        assertThat(getField(result, 0), Matchers.containsString(\"aaa\"));\n    }\n\n    @Test(expected = IllegalArgumentException.class)\n    public void testReorderMetadataFieldsOrderByIsRequired() throws Exception {\n        api.reorderMetadataFields(null, null, Collections.EMPTY_MAP);\n    }\n\n    private String getField(ApiResponse result, int index) {\n        String actual = ((Map)((ArrayList)result.get(\"metadata_fields\")).get(index)).get(\"label\").toString();\n        return actual;\n    }\n\n    private void AddStringField(String labelPrefix) throws Exception {\n        StringMetadataField field = newFieldInstance(labelPrefix, true);\n        ApiResponse fieldResult = addFieldToAccount(field);\n        String fieldId = fieldResult.get(\"external_id\").toString();\n    }\n\n    @Test\n    public void testUploadWithMetadata() throws Exception {\n        StringMetadataField field = newFieldInstance(\"testUploadWithMetadata\", true);\n        ApiResponse fieldResult = addFieldToAccount(field);\n        String fieldId = fieldResult.get(\"external_id\").toString();\n        Map<String, Object> metadata = Collections.<String, Object>singletonMap(fieldId, \"123456\");\n        Map result = cloudinary.uploader().upload(SRC_TEST_IMAGE, asMap(\"metadata\", metadata, \"tags\", Arrays.asList(SDK_TEST_TAG, METADATA_UPLOADER_TAG)));\n        assertNotNull(result.get(\"metadata\"));\n        assertEquals(\"123456\", ((Map) result.get(\"metadata\")).get(fieldId));\n    }\n\n    @Test\n    public void testExplicitWithMetadata() throws Exception {\n        Map uploadResult = cloudinary.uploader().upload(SRC_TEST_IMAGE, asMap(\"tags\", Arrays.asList(SDK_TEST_TAG, METADATA_UPLOADER_TAG)));\n        String publicId = uploadResult.get(\"public_id\").toString();\n        StringMetadataField field = newFieldInstance(\"testExplicitWithMetadata\", true);\n        ApiResponse fieldResult = addFieldToAccount(field);\n        String fieldId = fieldResult.get(\"external_id\").toString();\n        Map<String, Object> metadata = Collections.<String, Object>singletonMap(fieldId, \"123456\");\n        Map result = cloudinary.uploader().explicit(publicId, asMap(\"type\", \"upload\", \"resource_type\", \"image\", \"metadata\", metadata));\n        assertNotNull(result.get(\"metadata\"));\n        assertEquals(\"123456\", ((Map) result.get(\"metadata\")).get(fieldId));\n\n        // explicit with invalid data, should fail:\n        metadata = Collections.<String, Object>singletonMap(fieldId, \"12\");\n        String message = \"\";\n        try {\n            result = cloudinary.uploader().explicit(publicId, asMap(\"type\", \"upload\", \"resource_type\", \"image\", \"metadata\", metadata));\n        } catch (Exception e){\n            message = e.getMessage();\n        }\n\n        assertTrue(message.contains(\"is not valid for field\") );\n    }\n\n    @Test\n    public void testUpdateWithMetadata() throws Exception {\n        Map uploadResult = cloudinary.uploader().upload(SRC_TEST_IMAGE, asMap(\"tags\", Arrays.asList(SDK_TEST_TAG, METADATA_UPLOADER_TAG)));\n        String publicId = uploadResult.get(\"public_id\").toString();\n        StringMetadataField field = newFieldInstance(\"testUpdateWithMetadata\", true);\n        ApiResponse fieldResult = addFieldToAccount(field);\n        String fieldId = fieldResult.get(\"external_id\").toString();\n        Map<String, Object> metadata = Collections.<String, Object>singletonMap(fieldId, \"123456\");\n        Map result = cloudinary.api().update(publicId, asMap(\"type\", \"upload\", \"resource_type\", \"image\", \"metadata\", metadata));\n        assertNotNull(result.get(\"metadata\"));\n        assertEquals(\"123456\", ((Map) result.get(\"metadata\")).get(fieldId));\n    }\n\n    @Test\n    public void testUploaderUpdateMetadata() throws Exception {\n        StringMetadataField field = newFieldInstance(\"testUploaderUpdateMetadata\", true);\n        ApiResponse fieldResult = addFieldToAccount(field);\n        String fieldId = fieldResult.get(\"external_id\").toString();\n        Map result = cloudinary.uploader().updateMetadata(Collections.<String, Object>singletonMap(fieldId, \"123456\"), new String[]{PUBLIC_ID}, null);\n        assertNotNull(result);\n        assertEquals(PUBLIC_ID, ((List) result.get(\"public_ids\")).get(0).toString());\n        //test updateMetadata for private asset\n        Map result2 = cloudinary.uploader().updateMetadata(Collections.<String, Object>singletonMap(fieldId, \"123456\"), new String[]{PRIVATE_PUBLIC_ID}, asMap(\"type\",\"private\"));\n        assertNotNull(result);\n        assertEquals(PRIVATE_PUBLIC_ID, ((List) result2.get(\"public_ids\")).get(0).toString());\n    }\n\n    @Test\n    public void testUploaderUpdateMetadataClearInvalid() throws Exception {\n        StringMetadataField field = newFieldInstance(\"testUploaderUpdateMetadata1\", true);\n        ApiResponse fieldResult = addFieldToAccount(field);\n        String fieldId = fieldResult.get(\"external_id\").toString();\n        Map result = cloudinary.uploader().updateMetadata(Collections.<String, Object>singletonMap(fieldId, \"123456\"), new String[]{PUBLIC_ID}, ObjectUtils.asMap(\"clear_invalid\", true));\n        assertNotNull(result);\n    }\n\n    @Test\n    public void testSetField() throws Exception {\n        SetMetadataField field = createSetField(\"test123\");\n        ApiResponse fieldResult = addFieldToAccount(field);\n        String fieldId = fieldResult.get(\"external_id\").toString();\n        Map result = cloudinary.uploader().updateMetadata(asMap(fieldId, new String[]{\"id2\", \"id3\"}), new String[]{PUBLIC_ID}, null);\n        assertNotNull(result);\n        assertEquals(PUBLIC_ID, ((List) result.get(\"public_ids\")).get(0).toString());\n        List<String> list = new ArrayList<String>(2);\n        list.add(\"id1\");\n        list.add(\"id2\");\n        result = cloudinary.uploader().updateMetadata(asMap(fieldId, list), new String[]{PUBLIC_ID}, null);\n        assertNotNull(result);\n        assertEquals(PUBLIC_ID, ((List) result.get(\"public_ids\")).get(0).toString());\n    }\n\n    @Test\n    public void testListMetadataRules() throws Exception {\n        Assume.assumeTrue(MockableTest.shouldTestFeature(Feature.CONDITIONAL_METADATA_RULES));\n        ApiResponse result = cloudinary.api().listMetadataRules(null);\n        assertNotNull(result);\n    }\n\n    @Test\n    public void testAddMetadataRule() throws Exception {\n        Assume.assumeTrue(MockableTest.shouldTestFeature(Feature.CONDITIONAL_METADATA_RULES));\n        SetMetadataField field = createSetField(\"test123\");\n        ApiResponse response = addFieldToAccount(field);\n        assertNotNull(response);\n\n        String externalId = (String) response.get(\"external_id\");\n        MetadataRule rule = new MetadataRule(externalId, \"category-employee\", new MetadataRuleCondition(\"category\", false, null, \"employee\"), new MetadataRuleResult(true, \"all\", null, null));\n        ApiResponse result = cloudinary.api().addMetadataRule(rule, ObjectUtils.asMap());\n        assertNotNull(result);\n\n        String name = (String) result.get(\"name\");\n        assertEquals(name, \"category-employee\");\n    }\n\n    @Test\n    public void testUpdateMetadataRule() throws Exception {\n        Assume.assumeTrue(MockableTest.shouldTestFeature(Feature.CONDITIONAL_METADATA_RULES));\n        ApiResponse response = cloudinary.api().listMetadataRules(null);\n        List metadataRules = (List) response.get(\"metadata_rules\");\n        assertNotNull(metadataRules);\n        String externalId = (String) ((Map) metadataRules.get(0)).get(\"external_id\");\n\n        MetadataRule rule = new MetadataRule(null, \"test_name\", null, null);\n        ApiResponse result = cloudinary.api().updateMetadataRule(externalId, rule, ObjectUtils.asMap());\n        assertNotNull(result);\n    }\n\n    @Test\n    public void testDeleteMetadataRule() throws Exception {\n        Assume.assumeTrue(MockableTest.shouldTestFeature(Feature.CONDITIONAL_METADATA_RULES));\n        ApiResponse response = cloudinary.api().listMetadataRules(null);\n        List metadataRules = (List) response.get(\"metadata_rules\");\n        assertNotNull(metadataRules);\n        String externalId = (String) ((Map) metadataRules.get(0)).get(\"external_id\");\n\n        ApiResponse result = cloudinary.api().deleteMetadataRule(externalId, ObjectUtils.emptyMap());\n        assertNotNull(result);\n    }\n\n    // Metadata test helpers\n    private SetMetadataField createSetField(String labelPrefix) {\n        SetMetadataField setField = new SetMetadataField();\n        String label = labelPrefix + \"_\" + SUFFIX;\n        setField.setLabel(label);\n        setField.setMandatory(false);\n        setField.setAllowDynamicListValues(true);\n        setField.setValidation(new MetadataValidation.StringLength(3, 99));\n        setField.setDefaultValue(Arrays.asList(\"id2\", \"id3\"));\n        setField.setValidation(null);\n        List<MetadataDataSource.Entry> entries = new ArrayList<MetadataDataSource.Entry>();\n        entries.add(new MetadataDataSource.Entry(\"id1\", \"first_value\"));\n        entries.add(new MetadataDataSource.Entry(\"id2\", \"second_value\"));\n        entries.add(new MetadataDataSource.Entry(\"id3\", \"third_value\"));\n        MetadataDataSource dataSource = new MetadataDataSource(entries);\n        setField.setDataSource(dataSource);\n        return setField;\n    }\n\n    private StringMetadataField newFieldInstance(String labelPrefix, Boolean mandatory) throws Exception {\n        String label = labelPrefix + \"_\" + SUFFIX;\n        return MetadataTestHelper.newFieldInstance(label, mandatory);\n    }\n\n    private ApiResponse addFieldToAccount(MetadataField field) throws Exception {\n        ApiResponse apiResponse = MetadataTestHelper.addFieldToAccount(api, field);\n        metadataFieldExternalIds.add(apiResponse.get(\"external_id\").toString());\n        return apiResponse;\n    }\n}\n"
  },
  {
    "path": "cloudinary-test-common/src/main/java/com/cloudinary/test/AbstractUploaderTest.java",
    "content": "package com.cloudinary.test;\n\nimport com.cloudinary.*;\nimport com.cloudinary.metadata.StringMetadataField;\nimport com.cloudinary.test.rules.RetryRule;\nimport com.cloudinary.utils.ObjectUtils;\nimport com.cloudinary.utils.Rectangle;\nimport org.cloudinary.json.JSONArray;\nimport org.junit.*;\nimport org.junit.rules.TestName;\n\nimport java.io.*;\nimport java.net.HttpURLConnection;\nimport java.net.URL;\nimport java.text.ParseException;\nimport java.text.SimpleDateFormat;\nimport java.util.*;\nimport java.util.zip.ZipInputStream;\n\nimport static com.cloudinary.utils.ObjectUtils.*;\nimport static com.cloudinary.utils.StringUtils.isRemoteUrl;\nimport static org.hamcrest.Matchers.*;\nimport static org.junit.Assert.*;\nimport static org.junit.Assume.assumeNotNull;\n\n@SuppressWarnings({\"rawtypes\", \"unchecked\"})\nabstract public class AbstractUploaderTest extends MockableTest {\n    private static final String ARCHIVE_TAG = SDK_TEST_TAG + \"_archive\";\n    private static final String UPLOADER_TAG = SDK_TEST_TAG + \"_uploader\";\n    public static final int SRC_TEST_IMAGE_W = 241;\n    public static final int SRC_TEST_IMAGE_H = 51;\n    private static Map<String, Set<String>> toDelete = new HashMap<String, Set<String>>();\n    private static final String UPLOADER_TEST_PUBLIC_ID = \"uploader_test\";\n    public static final String SRC_FULLY_QUALIFIED_IMAGE=\"image/upload/\" + UPLOADER_TEST_PUBLIC_ID;\n    public static final String SRC_FULLY_QUALIFIED_VIDEO=\"video/upload/dog\";\n\tpublic static final String SRC_TEST_EVAL= \"if (resource_info['width'] < 450) { upload_options['quality_analysis'] = true };\" + \"upload_options['context'] = 'width=' + resource_info['width'];\";\n\n\n    @BeforeClass\n    public static void setUpClass() throws IOException {\n        Cloudinary cloudinary = new Cloudinary();\n        cloudinary.config.analytics = false;\n        if (cloudinary.config.apiSecret == null) {\n            System.err.println(\"Please setup environment for Upload test to run\");\n        }\n\n        cloudinary.uploader().upload(SRC_TEST_IMAGE, asMap(\"tags\", new String[]{SDK_TEST_TAG, UPLOADER_TAG, ARCHIVE_TAG}));\n        cloudinary.uploader().upload(SRC_TEST_IMAGE, asMap(\"tags\", new String[]{SDK_TEST_TAG, UPLOADER_TAG}, \"public_id\", UPLOADER_TEST_PUBLIC_ID, \"transformation\", \"f_jpg\"));\n        cloudinary.uploader().upload(SRC_TEST_VIDEO, asMap(\"tags\", new String[]{SDK_TEST_TAG, UPLOADER_TAG, ARCHIVE_TAG}, \"public_id\", \"dog\", \"resource_type\", \"video\"));\n        cloudinary.uploader().upload(SRC_TEST_IMAGE, asMap(\"tags\", new String[]{SDK_TEST_TAG, UPLOADER_TAG, ARCHIVE_TAG}, \"resource_type\", \"raw\"));\n        cloudinary.uploader().upload(SRC_TEST_IMAGE,\n                asMap(\"tags\", new String[]{SDK_TEST_TAG, UPLOADER_TAG, ARCHIVE_TAG},\n                        \"transformation\", new Transformation().crop(\"scale\").width(10)));\n    }\n\n    @AfterClass\n    public static void tearDownClass() {\n        Api api = new Cloudinary().api();\n        try {\n            api.deleteResourcesByTag(UPLOADER_TAG, ObjectUtils.emptyMap());\n        } catch (Exception ignored) {\n        }\n        try {\n            api.deleteResourcesByTag(UPLOADER_TAG, ObjectUtils.asMap(\"resource_type\", \"video\"));\n        } catch (Exception ignored) {\n        }\n        try {\n            api.deleteResourcesByTag(UPLOADER_TAG, ObjectUtils.asMap(\"resource_type\", \"raw\"));\n        } catch (Exception ignored) {\n        }\n        for (String type : toDelete.keySet()) {\n            try {\n                api.deleteResources(toDelete.get(type), Collections.singletonMap(\"type\", type));\n            } catch (Exception ignored) {\n            }\n        }\n\n        toDelete.clear();\n    }\n\n    @Rule\n    public TestName currentTest = new TestName();\n\n    @Rule\n    public RetryRule retryRule = new RetryRule();\n\n    @Before\n    public void setUp() {\n        System.out.println(\"Running \" + this.getClass().getName() + \".\" + currentTest.getMethodName());\n        this.cloudinary = new Cloudinary();\n        this.cloudinary.config.analytics = false;\n        assumeNotNull(cloudinary.config.apiSecret);\n    }\n\n\n    @Test\n    public void testUtf8Upload() throws IOException {\n\n        Map result = cloudinary.uploader().upload(SRC_TEST_IMAGE, asMap(\"colors\", true, \"tags\", Arrays.asList(SDK_TEST_TAG, UPLOADER_TAG), \"public_id\", \"aåßéƒ\"));\n        assertEquals(result.get(\"width\"), SRC_TEST_IMAGE_W);\n        assertEquals(result.get(\"height\"), SRC_TEST_IMAGE_H);\n        assertNotNull(result.get(\"colors\"));\n        assertNotNull(result.get(\"predominant\"));\n        Map<String, Object> to_sign = new HashMap<String, Object>();\n        to_sign.put(\"public_id\", result.get(\"public_id\"));\n        to_sign.put(\"version\", ObjectUtils.asString(result.get(\"version\")));\n        String expected_signature = cloudinary.apiSignRequest(to_sign, cloudinary.config.apiSecret, cloudinary.config.signatureVersion);\n        assertEquals(result.get(\"signature\"), expected_signature);\n    }\n\n    @Test\n    public void testDeleteByToken() throws Exception {\n        Map options = ObjectUtils.asMap(\"return_delete_token\", true, \"tags\", new String[]{SDK_TEST_TAG, UPLOADER_TAG});\n        Map res = cloudinary.uploader().upload(SRC_TEST_IMAGE, options);\n        String token = (String) res.get(\"delete_token\");\n        Map<String, Object> baseConfig = cloudinary.config.asMap();\n        baseConfig.remove(\"api_key\");\n        baseConfig.remove(\"api_secret\");\n        res = new Cloudinary(baseConfig).uploader().deleteByToken(token);\n        assertNotNull(res);\n        assertEquals(\"ok\", res.get(\"result\"));\n    }\n\n    @Test\n    public void testUpload() throws IOException {\n        Map result = cloudinary.uploader().upload(SRC_TEST_IMAGE, asMap(\"colors\", true, \"tags\", Arrays.asList(SDK_TEST_TAG, UPLOADER_TAG)));\n        assertEquals(result.get(\"width\"), SRC_TEST_IMAGE_W);\n        assertEquals(result.get(\"height\"), SRC_TEST_IMAGE_H);\n        assertNotNull(result.get(\"colors\"));\n        assertNotNull(result.get(\"predominant\"));\n        Map<String, Object> to_sign = new HashMap<String, Object>();\n        to_sign.put(\"public_id\", result.get(\"public_id\"));\n        to_sign.put(\"version\", ObjectUtils.asString(result.get(\"version\")));\n        String expected_signature = cloudinary.apiSignRequest(to_sign, cloudinary.config.apiSecret, cloudinary.config.signatureVersion);\n        assertEquals(result.get(\"signature\"), expected_signature);\n    }\n\n    @Test\n    public void testIsRemoteUrl() {\n        String[] urls = new String[]{\n                \"ftp://ftp.cloudinary.com/images/old_logo.png\",\n                \"http://cloudinary.com/images/old_logo.png\",\n                \"https://cloudinary.com/images/old_logo.png\",\n                \"s3://s3-us-west-2.amazonaws.com/cloudinary/images/old_logo.png\",\n                \"gs://cloudinary/images/old_logo.png\",\n                \"data:image/gif;charset=utf8;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7\",\n                \"data:image/gif;param1=value1;param2=value2;base64,\" +\n                        \"R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7\",\n                \"data:image/svg+xml;charset=utf-8;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPg\"};\n\n        for (String url : urls) {\n            assertTrue(isRemoteUrl(url));\n        }\n\n        String[] invalidUrls = new String[]{\"adsadasdasdasd\", \"     \", \"\"};\n\n        for (String url : invalidUrls) {\n            assertFalse(isRemoteUrl(url));\n        }\n    }\n\n    @Test\n    public void testUploadUrl() throws IOException {\n        Map result = cloudinary.uploader().upload(REMOTE_TEST_IMAGE, asMap(\"tags\", Arrays.asList(SDK_TEST_TAG, UPLOADER_TAG)));\n        assertEquals(result.get(\"width\"), SRC_TEST_IMAGE_W);\n        assertEquals(result.get(\"height\"), SRC_TEST_IMAGE_H);\n        Map<String, Object> to_sign = new HashMap<String, Object>();\n        to_sign.put(\"public_id\", result.get(\"public_id\"));\n        to_sign.put(\"version\", ObjectUtils.asString(result.get(\"version\")));\n        String expected_signature = cloudinary.apiSignRequest(to_sign, cloudinary.config.apiSecret, cloudinary.config.signatureVersion);\n        assertEquals(result.get(\"signature\"), expected_signature);\n    }\n\n    @Test\n    public void testUploadLargeUrl() throws IOException {\n        Map result = cloudinary.uploader().uploadLarge(REMOTE_TEST_IMAGE, asMap(\"tags\", Arrays.asList(SDK_TEST_TAG, UPLOADER_TAG)));\n        assertEquals(result.get(\"width\"), SRC_TEST_IMAGE_W);\n        assertEquals(result.get(\"height\"), SRC_TEST_IMAGE_H);\n        Map<String, Object> to_sign = new HashMap<String, Object>();\n        to_sign.put(\"public_id\", result.get(\"public_id\"));\n        to_sign.put(\"version\", ObjectUtils.asString(result.get(\"version\")));\n        String expected_signature = cloudinary.apiSignRequest(to_sign, cloudinary.config.apiSecret, cloudinary.config.signatureVersion);\n        assertEquals(result.get(\"signature\"), expected_signature);\n    }\n\n    @Test\n    public void testUploadDataUri() throws IOException {\n        Map result = cloudinary.uploader().upload(\"data:image/png;base64,iVBORw0KGgoAA\\nAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAABlBMVEUAAAD///+l2Z/dAAAAM0l\\nEQVR4nGP4/5/h/1+G/58ZDrAz3D/McH8yw83NDDeNGe4Ug9C9zwz3gVLMDA/A6\\nP9/AFGGFyjOXZtQAAAAAElFTkSuQmCC\", asMap(\"tags\", Arrays.asList(SDK_TEST_TAG, UPLOADER_TAG)));\n        assertEquals(result.get(\"width\"), 16);\n        assertEquals(result.get(\"height\"), 16);\n        Map<String, Object> to_sign = new HashMap<String, Object>();\n        to_sign.put(\"public_id\", result.get(\"public_id\"));\n        to_sign.put(\"version\", ObjectUtils.asString(result.get(\"version\")));\n        String expected_signature = cloudinary.apiSignRequest(to_sign, cloudinary.config.apiSecret, cloudinary.config.signatureVersion);\n        assertEquals(result.get(\"signature\"), expected_signature);\n    }\n\n    @Test\n    public void testUploadUTF8() throws IOException {\n        Map result = cloudinary.uploader().upload(\"../cloudinary-test-common/src/main/resources/old_logo.png\", asMap(\"public_id\", \"Plattenkreiss_ñg-é\", \"tags\", Arrays.asList(SDK_TEST_TAG, UPLOADER_TAG)));\n        assertEquals(result.get(\"public_id\"), \"Plattenkreiss_ñg-é\");\n        cloudinary.uploader().upload(result.get(\"url\"), asMap(\"tags\", Arrays.asList(SDK_TEST_TAG, UPLOADER_TAG)));\n    }\n\n    @Test\n    public void testRename() throws Exception {\n        Map result = cloudinary.uploader().upload(SRC_TEST_IMAGE, asMap(\"tags\", Arrays.asList(SDK_TEST_TAG, UPLOADER_TAG)));\n\n        Object publicId = result.get(\"public_id\");\n        String publicId2 = \"folder/\" + publicId + \"2\";\n        cloudinary.uploader().rename((String) publicId, publicId2, ObjectUtils.emptyMap());\n        assertNotNull(cloudinary.api().resource(publicId2, ObjectUtils.emptyMap()));\n\n        Map result2 = cloudinary.uploader().upload(\"../cloudinary-test-common/src/main/resources/favicon.ico\", asMap(\"tags\", Arrays.asList(SDK_TEST_TAG, UPLOADER_TAG)));\n        boolean error_found = false;\n        try {\n            cloudinary.uploader().rename((String) result2.get(\"public_id\"), publicId2, asMap(\"tags\", Arrays.asList(SDK_TEST_TAG, UPLOADER_TAG)));\n        } catch (Exception e) {\n            error_found = true;\n        }\n        assertTrue(error_found);\n        cloudinary.uploader().rename((String) result2.get(\"public_id\"), publicId2, asMap(\"overwrite\", true, \"tags\", Arrays.asList(SDK_TEST_TAG, UPLOADER_TAG)));\n        assertEquals(cloudinary.api().resource(publicId2, ObjectUtils.emptyMap()).get(\"format\"), \"ico\");\n    }\n\n    @Test\n    public void testRenameShouldReturnContext() throws Exception {\n        Map result = cloudinary.uploader().upload(SRC_TEST_IMAGE, asMap(\"tags\", Arrays.asList(SDK_TEST_TAG, UPLOADER_TAG), \"context\", asMap(\"foo\", \"boo\")));\n\n        String publicId = result.get(\"public_id\").toString();\n        String publicId2 = \"folder/\" + publicId + \"2\";\n        Map renameResult = cloudinary.uploader().rename(publicId, publicId2, asMap(\"context\", true));\n        assertNotNull(renameResult.get(\"context\"));\n    }\n\n    @Test\n    public void testRenameShouldReturnMetadata() throws Exception {\n        String label = \"test\" + SUFFIX;\n        StringMetadataField f = MetadataTestHelper.newFieldInstance(label, true);\n        Map fieldResult = MetadataTestHelper.addFieldToAccount(cloudinary.api(), f);\n        String fieldId = fieldResult.get(\"external_id\").toString();\n        Map<String, Object> metadata = Collections.<String, Object>singletonMap(fieldId, \"123456\");\n        Map result = cloudinary.uploader().upload(SRC_TEST_IMAGE, asMap(\"tags\", Arrays.asList(SDK_TEST_TAG, UPLOADER_TAG), \"metadata\", metadata));\n\n        String publicId = result.get(\"public_id\").toString();\n        String publicId2 = \"folder/\" + publicId + \"2\";\n        Map renameResult = cloudinary.uploader().rename(publicId, publicId2, asMap(\"metadata\", true));\n        assertNotNull(renameResult.get(\"metadata\"));\n    }\n\n    @Test\n    public void testUniqueFilename() throws Exception {\n        Map result = cloudinary.uploader().upload(SRC_TEST_IMAGE, asMap(\"use_filename\", true, \"tags\", Arrays.asList(SDK_TEST_TAG, UPLOADER_TAG)));\n        assertTrue(((String) result.get(\"public_id\")).matches(\"old_logo_[a-z0-9]{6}\"));\n        result = cloudinary.uploader().upload(SRC_TEST_IMAGE, asMap(\"use_filename\", true, \"unique_filename\", false, \"tags\", Arrays.asList(SDK_TEST_TAG, UPLOADER_TAG)));\n        assertEquals(result.get(\"public_id\"), \"old_logo\");\n    }\n\n    @Test\n    public void testExplicit() throws IOException {\n        Map result = cloudinary.uploader().explicit(UPLOADER_TEST_PUBLIC_ID, asMap(\"eager\", Collections.singletonList(new Transformation().crop(\"scale\").width(2.0)), \"type\", \"upload\", \"moderation\", \"manual\"));\n        String url = cloudinary.url().transformation(new Transformation().crop(\"scale\").width(2.0)).format(\"jpg\").version(result.get(\"version\")).generate(UPLOADER_TEST_PUBLIC_ID);\n        String eagerUrl = (String) ((Map) ((List) result.get(\"eager\")).get(0)).get(\"url\");\n        String cloudName = cloudinary.config.cloudName;\n        assertEquals(eagerUrl.substring(eagerUrl.indexOf(cloudName)), url.substring(url.indexOf(cloudName)));\n    }\n\n    @Test\n    public void testEager() throws IOException {\n        cloudinary.uploader().upload(SRC_TEST_IMAGE, asMap(\"eager\", Collections.singletonList(new Transformation().crop(\"scale\").width(2.0)), \"tags\", Arrays.asList(SDK_TEST_TAG, UPLOADER_TAG)));\n    }\n\n    @Test\n    public void testUploadAsync() throws IOException {\n        Map result = cloudinary.uploader().upload(SRC_TEST_IMAGE, asMap(\"transformation\", new Transformation().crop(\"scale\").width(2.0), \"async\", true, \"tags\", Arrays.asList(SDK_TEST_TAG, UPLOADER_TAG)));\n        assertEquals((String) result.get(\"status\"), \"pending\");\n    }\n\n    @Test\n    public void testHeaders() throws IOException {\n        cloudinary.uploader().upload(SRC_TEST_IMAGE, asMap(\"headers\", new String[]{\"Link: 1\"}, \"tags\", Arrays.asList(SDK_TEST_TAG, UPLOADER_TAG)));\n        cloudinary.uploader().upload(SRC_TEST_IMAGE, asMap(\"headers\", asMap(\"Link\", \"1\"), \"tags\", Arrays.asList(SDK_TEST_TAG, UPLOADER_TAG)));\n    }\n\n    @Test\n    public void testText() throws Exception {\n        Map result = cloudinary.uploader().text(\"hello world\", asMap(\"tags\", Arrays.asList(SDK_TEST_TAG, UPLOADER_TAG)));\n        addToDeleteList(\"text\", result.get(\"public_id\").toString());\n        assertTrue(((Integer) result.get(\"width\")) > 1);\n        assertTrue(((Integer) result.get(\"height\")) > 1);\n    }\n\n    @Test\n    public void testImageUploadTag() {\n        String tag = cloudinary.uploader().imageUploadTag(\"test-field\", asMap(\"callback\", \"http://localhost/cloudinary_cors.html\"), asMap(\"htmlattr\", \"htmlvalue\"));\n        assertTrue(tag.contains(\"type='file'\"));\n        assertTrue(tag.contains(\"data-cloudinary-field='test-field'\"));\n        assertTrue(tag.contains(\"class='cloudinary-fileupload'\"));\n        assertTrue(tag.contains(\"htmlattr='htmlvalue'\"));\n        tag = cloudinary.uploader().imageUploadTag(\"test-field\", asMap(\"callback\", \"http://localhost/cloudinary_cors.html\"), asMap(\"class\", \"myclass\"));\n        assertTrue(tag.contains(\"class='cloudinary-fileupload myclass'\"));\n    }\n\n    @Test\n    public void testEvalUploadParameter() throws IOException {\n       Map result = cloudinary.uploader().upload(SRC_TEST_IMAGE, asMap(\n               \"eval\",SRC_TEST_EVAL,\n               \"tags\", Arrays.asList(SDK_TEST_TAG, UPLOADER_TAG)\n               ));\n       assertTrue(result.get(\"quality_analysis\")!=null && \n    \t\t   ((HashMap)result.get(\"quality_analysis\")).containsKey(\"focus\"));\n       Map custom= (Map)((Map) result.get(\"context\")).get(\"custom\");\n       assertEquals(custom.get(\"width\"),Integer.toString(SRC_TEST_IMAGE_W));\n    }\n\n    @Test\n    public void testSprite() throws Exception {\n        final String sprite_test_tag = String.format(\"sprite_test_tag_%d\", new java.util.Date().getTime());\n        Map uploadResult1 = cloudinary.uploader().upload(SRC_TEST_IMAGE, asMap(\"tags\", new String[]{sprite_test_tag, SDK_TEST_TAG, UPLOADER_TAG}, \"public_id\", \"sprite_test_tag_1\" + SUFFIX));\n        Map uploadResult2 = cloudinary.uploader().upload(SRC_TEST_IMAGE, asMap(\"tags\", new String[]{sprite_test_tag, SDK_TEST_TAG, UPLOADER_TAG}, \"public_id\", \"sprite_test_tag_2\" + SUFFIX));\n\n        String[] urls = new String[]{uploadResult1.get(\"url\").toString(), uploadResult2.get(\"url\").toString()};\n\n        Map result = cloudinary.uploader().generateSprite(urls, asMap(\"tags\", Arrays.asList(SDK_TEST_TAG, UPLOADER_TAG)));\n        addToDeleteList(\"sprite\", result.get(\"public_id\").toString());\n        assertEquals(2, ((Map) result.get(\"image_infos\")).size());\n\n        result = cloudinary.uploader().generateSprite(sprite_test_tag, asMap(\"tags\", Arrays.asList(SDK_TEST_TAG, UPLOADER_TAG)));\n        addToDeleteList(\"sprite\", result.get(\"public_id\").toString());\n        assertEquals(2, ((Map) result.get(\"image_infos\")).size());\n        result = cloudinary.uploader().generateSprite(sprite_test_tag, asMap(\"transformation\", \"w_100\"));\n        addToDeleteList(\"sprite\", result.get(\"public_id\").toString());\n        assertTrue(((String) result.get(\"css_url\")).contains(\"w_100\"));\n        result = cloudinary.uploader().generateSprite(sprite_test_tag, asMap(\"transformation\", new Transformation().width(100), \"format\", \"jpg\"));\n        addToDeleteList(\"sprite\", result.get(\"public_id\").toString());\n        assertTrue(((String) result.get(\"css_url\")).contains(\"f_jpg,w_100\"));\n    }\n\n    @Test\n    public void testMulti() throws Exception {\n        final String MULTI_TEST_TAG = \"multi_test_tag\" + SUFFIX;\n        final Map options = asMap(\"tags\", new String[]{MULTI_TEST_TAG, SDK_TEST_TAG, UPLOADER_TAG});\n        Map uploadResult1 = cloudinary.uploader().upload(SRC_TEST_IMAGE, options);\n        Map uploadResult2 = cloudinary.uploader().upload(SRC_TEST_IMAGE, options);\n\n        String[] urls = new String[]{uploadResult1.get(\"url\").toString(), uploadResult2.get(\"url\").toString()};\n\n        Map result = cloudinary.uploader().multi(urls, asMap(\"transformation\", \"c_crop,w_0.5\"));\n        addToDeleteList(\"multi\", result.get(\"public_id\").toString());\n\n        assertTrue(((String) result.get(\"url\")).endsWith(\".gif\"));\n        assertTrue(((String) result.get(\"url\")).contains(\"w_0.5\"));\n\n        List<String> ids = new ArrayList<String>();\n        result = cloudinary.uploader().multi(MULTI_TEST_TAG, asMap(\"transformation\", \"c_crop,w_0.5\"));\n        addToDeleteList(\"multi\", result.get(\"public_id\").toString());\n        Map pdfResult = cloudinary.uploader().multi(MULTI_TEST_TAG, asMap(\"transformation\", new Transformation().width(111), \"format\", \"pdf\"));\n        addToDeleteList(\"multi\", pdfResult.get(\"public_id\").toString());\n\n        assertTrue(((String) result.get(\"url\")).endsWith(\".gif\"));\n        assertTrue(((String) result.get(\"url\")).contains(\"w_0.5\"));\n        assertTrue(((String) pdfResult.get(\"url\")).contains(\"w_111\"));\n        assertTrue(((String) pdfResult.get(\"url\")).endsWith(\".pdf\"));\n    }\n\n    @Test\n    public void testTags() throws Exception {\n        Map result = cloudinary.uploader().upload(SRC_TEST_IMAGE, ObjectUtils.emptyMap());\n        String public_id = (String) result.get(\"public_id\");\n        addToDeleteList(\"upload\", public_id);\n        Map result2 = cloudinary.uploader().upload(SRC_TEST_IMAGE, ObjectUtils.emptyMap());\n        String public_id2 = (String) result2.get(\"public_id\");\n        addToDeleteList(\"upload\", public_id2);\n\n        //Test add tags\n        cloudinary.uploader().addTag(\"tag1\", new String[]{public_id, public_id2}, ObjectUtils.emptyMap());\n        cloudinary.uploader().addTag(\"tag2\", new String[]{public_id}, ObjectUtils.emptyMap());\n        cloudinary.uploader().addTag(new String[]{\"tag4\",\"tag5\"}, new String[]{public_id}, ObjectUtils.emptyMap());\n        List<String> tags = (List<String>) cloudinary.api().resource(public_id, ObjectUtils.emptyMap()).get(\"tags\");\n        assertEquals(tags, asArray(new String[]{\"tag1\", \"tag2\", \"tag4\", \"tag5\"}));\n        tags = (List<String>) cloudinary.api().resource(public_id2, ObjectUtils.emptyMap()).get(\"tags\");\n        assertEquals(tags, asArray(new String[]{\"tag1\"}));\n\n        //Test remove tags\n        cloudinary.uploader().removeTag(\"tag1\", new String[]{public_id}, ObjectUtils.emptyMap());\n        tags = (List<String>) cloudinary.api().resource(public_id, ObjectUtils.emptyMap()).get(\"tags\");\n        assertEquals(tags, asArray(new String[]{\"tag2\", \"tag4\", \"tag5\"}));\n        cloudinary.uploader().removeTag(new String[]{\"tag4\", \"tag5\"}, new String[]{public_id}, ObjectUtils.emptyMap());\n        tags = (List<String>) cloudinary.api().resource(public_id, ObjectUtils.emptyMap()).get(\"tags\");\n        assertEquals(tags, asArray(new String[]{\"tag2\"}));\n\n        //Test replace tags\n        cloudinary.uploader().replaceTag(\"tag3\", new String[]{public_id}, ObjectUtils.emptyMap());\n        tags = (List<String>) cloudinary.api().resource(public_id, ObjectUtils.emptyMap()).get(\"tags\");\n        assertEquals(tags, asArray(new String[]{\"tag3\"}));\n        cloudinary.uploader().replaceTag(new String[]{\"tag6\", \"tag7\"}, new String[]{public_id}, ObjectUtils.emptyMap());\n        tags = (List<String>) cloudinary.api().resource(public_id, ObjectUtils.emptyMap()).get(\"tags\");\n        assertEquals(tags, asArray(new String[]{\"tag6\", \"tag7\"}));\n\n        //Test remove all tags\n        result = cloudinary.uploader().removeAllTags(new String[]{public_id, public_id2, \"noSuchId\"}, ObjectUtils.emptyMap());\n        List<String> publicIds = (List<String>) result.get(\"public_ids\");\n        assertThat(publicIds, containsInAnyOrder(public_id, public_id2)); // = and not containing \"noSuchId\"\n        result = cloudinary.api().resource(public_id, ObjectUtils.emptyMap());\n        assertThat((Map<? extends String, ?>) result, not(hasKey(\"tags\")));\n    }\n\n    @Test\n    public void testAllowedFormats() throws Exception {\n        //should allow whitelisted formats if allowed_formats\n        String[] formats = {\"png\"};\n        Map result = cloudinary.uploader().upload(SRC_TEST_IMAGE, asMap(\"allowed_formats\", formats, \"tags\", Arrays.asList(SDK_TEST_TAG, UPLOADER_TAG)));\n        assertEquals(result.get(\"format\"), \"png\");\n    }\n\n    @Test\n    public void testAllowedFormatsWithIllegalFormat() throws Exception {\n        //should prevent non whitelisted formats from being uploaded if allowed_formats is specified\n        boolean errorFound = false;\n        String[] formats = {\"jpg\"};\n        try {\n            cloudinary.uploader().upload(SRC_TEST_IMAGE, asMap(\"allowed_formats\", formats, \"tags\", Arrays.asList(SDK_TEST_TAG, UPLOADER_TAG)));\n        } catch (Exception e) {\n            errorFound = true;\n        }\n        assertTrue(errorFound);\n    }\n\n    @Test\n    public void testAllowedFormatsWithFormat() throws Exception {\n        //should allow non whitelisted formats if type is specified and convert to that type\n        String[] formats = {\"jpg\"};\n        Map result = cloudinary.uploader().upload(SRC_TEST_IMAGE, asMap(\"allowed_formats\", formats, \"format\", \"jpg\", \"tags\", Arrays.asList(SDK_TEST_TAG, UPLOADER_TAG)));\n        assertEquals(\"jpg\", result.get(\"format\"));\n    }\n\n    @Test\n    public void testFaceCoordinates() throws Exception {\n        //should allow sending face coordinates\n        Coordinates coordinates = new Coordinates();\n        Rectangle rect1 = new Rectangle(121, 31, 110, 51);\n        Rectangle rect2 = new Rectangle(120, 30, 109, 51);\n        coordinates.addRect(rect1);\n        coordinates.addRect(rect2);\n        Map result = cloudinary.uploader().upload(SRC_TEST_IMAGE, asMap(\"face_coordinates\", coordinates, \"faces\", true, \"tags\", Arrays.asList(SDK_TEST_TAG, UPLOADER_TAG)));\n        ArrayList resultFaces = ((ArrayList) result.get(\"faces\"));\n        assertEquals(2, resultFaces.size());\n\n        Object[] resultCoordinates = ((ArrayList) resultFaces.get(0)).toArray();\n\n        assertEquals(rect1.x, resultCoordinates[0]);\n        assertEquals(rect1.y, resultCoordinates[1]);\n        assertEquals(rect1.width, resultCoordinates[2]);\n        assertEquals(rect1.height, resultCoordinates[3]);\n\n        resultCoordinates = ((ArrayList) resultFaces.get(1)).toArray();\n\n        assertEquals(rect2.x, resultCoordinates[0]);\n        assertEquals(rect2.y, resultCoordinates[1]);\n        assertEquals(rect2.width, resultCoordinates[2]);\n        assertEquals(rect2.height, resultCoordinates[3]);\n\n        Coordinates differentCoordinates = new Coordinates();\n        Rectangle rect3 = new Rectangle(122, 32, 111, 152);\n        differentCoordinates.addRect(rect3);\n        cloudinary.uploader().explicit((String) result.get(\"public_id\"), asMap(\"face_coordinates\", differentCoordinates, \"faces\", true, \"type\", \"upload\"));\n        Map info = cloudinary.api().resource((String) result.get(\"public_id\"), asMap(\"faces\", true));\n\n        resultFaces = (ArrayList) info.get(\"faces\");\n        assertEquals(1, resultFaces.size());\n        resultCoordinates = ((ArrayList) resultFaces.get(0)).toArray();\n\n        assertEquals(rect3.x, resultCoordinates[0]);\n        assertEquals(rect3.y, resultCoordinates[1]);\n        assertEquals(rect3.width, resultCoordinates[2]);\n        assertEquals(rect3.height, resultCoordinates[3]);\n\n    }\n\n    @Test\n    public void testCustomCoordinates() throws Exception {\n        //should allow sending face coordinates\n        Coordinates coordinates = new Coordinates(\"121,31,300,151\");\n        Map uploadResult = cloudinary.uploader().upload(SRC_TEST_IMAGE, asMap(\"custom_coordinates\", coordinates, \"tags\", Arrays.asList(SDK_TEST_TAG, UPLOADER_TAG)));\n        Map result = cloudinary.api().resource(uploadResult.get(\"public_id\").toString(), asMap(\"coordinates\", true));\n        int[] expected = new int[]{121, 31, SRC_TEST_IMAGE_W, SRC_TEST_IMAGE_H};\n        Object[] actual = ((ArrayList) ((ArrayList) ((Map) result.get(\"coordinates\")).get(\"custom\")).get(0)).toArray();\n        for (int i = 0; i < expected.length; i++) {\n            assertEquals(expected[i], actual[i]);\n        }\n\n        coordinates = new Coordinates(new int[]{122, 32, SRC_TEST_IMAGE_W + 100, SRC_TEST_IMAGE_H + 100});\n        cloudinary.uploader().explicit((String) uploadResult.get(\"public_id\"), asMap(\"custom_coordinates\", coordinates, \"coordinates\", true, \"type\", \"upload\"));\n        result = cloudinary.api().resource(uploadResult.get(\"public_id\").toString(), asMap(\"coordinates\", true));\n        expected = new int[]{122, 32, SRC_TEST_IMAGE_W + 100, SRC_TEST_IMAGE_H + 100};\n        actual = ((ArrayList) ((ArrayList) ((Map) result.get(\"coordinates\")).get(\"custom\")).get(0)).toArray();\n        for (int i = 0; i < expected.length; i++) {\n            assertEquals(expected[i], actual[i]);\n        }\n    }\n\n    @Test\n    public void testModerationRequest() throws Exception {\n        //should support requesting manual moderation\n        Map result = cloudinary.uploader().upload(SRC_TEST_IMAGE, asMap(\"moderation\", \"manual\", \"tags\", Arrays.asList(SDK_TEST_TAG, UPLOADER_TAG)));\n        assertEquals(\"manual\", ((List<Map>) result.get(\"moderation\")).get(0).get(\"kind\"));\n        assertEquals(\"pending\", ((List<Map>) result.get(\"moderation\")).get(0).get(\"status\"));\n    }\n\n\n    @Test\n    public void testRawConvertRequest() {\n        //should support requesting raw conversion\n        try {\n            cloudinary.uploader().upload(SRC_TEST_IMAGE, asMap(\"raw_convert\", \"illegal\", \"tags\", Arrays.asList(SDK_TEST_TAG, UPLOADER_TAG)));\n        } catch (Exception e) {\n            assertTrue(e.getMessage().contains(\"Raw convert is invalid\"));\n        }\n    }\n\n    @Test\n    public void testCategorizationRequest() {\n        //should support requesting categorization\n        String errorMessage = \"\";\n\n        try {\n            cloudinary.uploader().upload(SRC_TEST_IMAGE, asMap(\"categorization\", \"illegal\", \"tags\", Arrays.asList(SDK_TEST_TAG, UPLOADER_TAG)));\n        } catch (Exception e) {\n            errorMessage = e.getMessage();\n        }\n\n        assertTrue(errorMessage.contains(\"Categorization item illegal is not valid\"));\n    }\n\n    @Test\n    public void testDetectionRequest() {\n        //should support requesting detection\n        String message = null;\n        try {\n            cloudinary.uploader().upload(SRC_TEST_IMAGE, asMap(\"detection\", \"illegal\", \"tags\", Arrays.asList(SDK_TEST_TAG, UPLOADER_TAG)));\n        } catch (Exception e) {\n            message = e.getMessage();\n        }\n\n        assertTrue(\"Detection invalid model 'illegal'\".equals(message));\n    }\n\n    @Test\n    public void testUploadLarge() throws Exception {\n        // support uploading large files\n\n        File temp = File.createTempFile(\"cldupload.test.\", \"\");\n        FileOutputStream out = new FileOutputStream(temp);\n        int[] header = new int[]{0x42, 0x4D, 0x4A, 0xB9, 0x59, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8A, 0x00, 0x00, 0x00, 0x7C, 0x00, 0x00, 0x00, 0x78, 0x05, 0x00, 0x00, 0x78, 0x05, 0x00, 0x00, 0x01, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0xB8, 0x59, 0x00, 0x61, 0x0F, 0x00, 0x00, 0x61, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0xFF, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x42, 0x47, 0x52, 0x73, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x54, 0xB8, 0x1E, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0x66, 0x66, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC4, 0xF5, 0x28, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};\n        byte[] byteHeader = new byte[138];\n        for (int i = 0; i <= 137; i++) byteHeader[i] = (byte) header[i];\n        byte[] piece = new byte[10];\n        Arrays.fill(piece, (byte) 0xff);\n        out.write(byteHeader);\n        for (int i = 1; i <= 588000; i++) {\n            out.write(piece);\n        }\n        out.close();\n        assertEquals(5880138, temp.length());\n\n        String[] tags = new String[]{\"upload_large_tag_\" + SUFFIX, SDK_TEST_TAG, UPLOADER_TAG};\n\n        Map resource = cloudinary.uploader().uploadLarge(temp, asMap(\"use_filename\", true, \"resource_type\", \"raw\", \"chunk_size\", 5243000, \"tags\", tags));\n        assertArrayEquals(tags, ((java.util.ArrayList) resource.get(\"tags\")).toArray());\n\n        assertEquals(\"raw\", resource.get(\"resource_type\"));\n        assertTrue(resource.get(\"public_id\").toString().startsWith(\"cldupload\"));\n\n        resource = cloudinary.uploader().uploadLarge(new FileInputStream(temp), asMap(\"filename\", \"test123\", \"chunk_size\", 5243000, \"tags\", tags));\n        assertArrayEquals(tags, ((java.util.ArrayList) resource.get(\"tags\")).toArray());\n        assertEquals(\"image\", resource.get(\"resource_type\"));\n        assertEquals(1400, resource.get(\"width\"));\n        assertEquals(1400, resource.get(\"height\"));\n        assertEquals(\"test123\", resource.get(\"original_filename\"));\n\n        resource = cloudinary.uploader().uploadLarge(temp, asMap(\"chunk_size\", 5880138, \"tags\", tags));\n        assertArrayEquals(tags, ((java.util.ArrayList) resource.get(\"tags\")).toArray());\n        assertEquals(\"image\", resource.get(\"resource_type\"));\n        assertEquals(1400, resource.get(\"width\"));\n        assertEquals(1400, resource.get(\"height\"));\n\n        resource = cloudinary.uploader().uploadLarge(new FileInputStream(temp), asMap(\"chunk_size\", 5880138, \"tags\", tags));\n        assertArrayEquals(tags, ((java.util.ArrayList) resource.get(\"tags\")).toArray());\n        assertEquals(\"image\", resource.get(\"resource_type\"));\n        assertEquals(1400, resource.get(\"width\"));\n        assertEquals(1400, resource.get(\"height\"));\n    }\n\n    @Test\n    public void testUnsignedUpload() throws Exception {\n        // should support unsigned uploading using presets\n        Map preset = cloudinary.api().createUploadPreset(asMap(\"folder\", \"upload_folder\", \"unsigned\", true));\n        Map result = cloudinary.uploader().unsignedUpload(SRC_TEST_IMAGE, preset.get(\"name\").toString(), asMap(\"tags\", Arrays.asList(SDK_TEST_TAG, UPLOADER_TAG)));\n        assertTrue(result.get(\"public_id\").toString().matches(\"^upload_folder\\\\/[a-z0-9]+$\"));\n        cloudinary.api().deleteUploadPreset(preset.get(\"name\").toString(), ObjectUtils.emptyMap());\n    }\n\n    @Test\n    public void testFilenameOption() throws Exception {\n        Map result = cloudinary.uploader().upload(SRC_TEST_IMAGE, asMap(\"filename\", \"emanelif\", \"tags\", Arrays.asList(SDK_TEST_TAG, UPLOADER_TAG)));\n        assertEquals(\"emanelif\", result.get(\"original_filename\"));\n    }\n\n    \n    @Test\n    public void testFilenameOverrideOption() throws Exception {\n        Map result = cloudinary.uploader().upload(SRC_TEST_IMAGE, asMap(\"filename_override\", \"overridden\", \"tags\", Arrays.asList(SDK_TEST_TAG, UPLOADER_TAG)));\n        assertEquals(\"overridden\", result.get(\"original_filename\"));\n    }\n\n    \n    @Test\n    public void testResponsiveBreakpoints() throws Exception {\n        ResponsiveBreakpoint breakpoint = new ResponsiveBreakpoint()\n                .createDerived(true)\n                .maxImages(2)\n                .transformation(new Transformation().angle(90))\n                .format(\"gif\");\n\n        // A single breakpoint\n        Map result = cloudinary.uploader().upload(SRC_TEST_IMAGE, asMap(\"responsive_breakpoints\",\n                breakpoint, \"tags\", Arrays.asList(SDK_TEST_TAG, UPLOADER_TAG)));\n\n        java.util.ArrayList breakpointsResponse = (java.util.ArrayList) result.get(\"responsive_breakpoints\");\n        Map map = (Map) breakpointsResponse.get(0);\n\n        java.util.ArrayList breakpoints = (java.util.ArrayList) map.get(\"breakpoints\");\n        assertTrue(((Map) breakpoints.get(0)).get(\"url\").toString().endsWith(\"gif\"));\n        assertEquals(\"a_90\", map.get(\"transformation\"));\n\n        // check again with transformation + format\n        breakpoint.transformation(new Transformation().effect(\"sepia\"));\n\n        // an array of breakpoints\n        result = cloudinary.uploader().upload(SRC_TEST_IMAGE, asMap(\"responsive_breakpoints\",\n                new ResponsiveBreakpoint[]{breakpoint}, \"tags\", Arrays.asList(SDK_TEST_TAG, UPLOADER_TAG)\n        ));\n        breakpointsResponse = (java.util.ArrayList) result.get(\"responsive_breakpoints\");\n        breakpoints = (java.util.ArrayList) ((Map) breakpointsResponse.get(0)).get(\"breakpoints\");\n        assertEquals(2, breakpoints.size());\n        assertTrue(((Map) breakpoints.get(0)).get(\"url\").toString().endsWith(\"gif\"));\n\n        // a JSONArray of breakpoints\n        JSONArray array = new JSONArray();\n        array.put(breakpoint);\n        result = cloudinary.uploader().upload(SRC_TEST_IMAGE, asMap(\"responsive_breakpoints\", array, \"tags\", Arrays.asList(SDK_TEST_TAG, UPLOADER_TAG)\n        ));\n        breakpointsResponse = (java.util.ArrayList) result.get(\"responsive_breakpoints\");\n        breakpoints = (java.util.ArrayList) ((Map) breakpointsResponse.get(0)).get(\"breakpoints\");\n        assertEquals(2, breakpoints.size());\n    }\n\n    @Test\n    public void testCreateArchive() throws Exception {\n        List<String> toDelete = new ArrayList<String>(2);\n        Map result = cloudinary.uploader().createArchive(new ArchiveParams().tags(new String[]{ARCHIVE_TAG}));\n        toDelete.add(result.get(\"public_id\").toString());\n        assertEquals(2, result.get(\"file_count\"));\n        result = cloudinary.uploader().createArchive(\n                new ArchiveParams().tags(new String[]{ARCHIVE_TAG}).transformations(\n                        new Transformation[]{new Transformation().width(0.5), new Transformation().width(2.0)}));\n        toDelete.add(result.get(\"public_id\").toString());\n\n        assertEquals(4, result.get(\"file_count\"));\n        cloudinary.api().deleteResources(toDelete, asMap(\"resource_type\", \"raw\"));\n    }\n\n\n    @Test\n    public void testCreateArchiveRaw() throws Exception {\n        Map result = cloudinary.uploader().createArchive(new ArchiveParams().tags(new String[]{ARCHIVE_TAG}).resourceType(\"raw\"));\n        assertEquals(1, result.get(\"file_count\"));\n        cloudinary.api().deleteResources(Arrays.asList(result.get(\"public_id\").toString()), asMap(\"resource_type\", \"raw\"));\n\n    }\n\n    @Test\n    public void testCreateZipMultipleResourceTypes() throws Exception {\n        Map result = cloudinary.uploader().createZip(ObjectUtils.asMap(\"fully_qualified_public_ids\",(new String[]{SRC_FULLY_QUALIFIED_IMAGE,SRC_FULLY_QUALIFIED_VIDEO}),\"resource_type\",\"auto\"));\n        assertEquals(2, result.get(\"file_count\"));\n        cloudinary.api().deleteResources(Arrays.asList(result.get(\"public_id\").toString()), asMap(\"resource_type\", \"raw\"));\n    }\n\n    @Test\n    public void testDownloadArchive() throws Exception {\n        String result = cloudinary.downloadArchive(new ArchiveParams().tags(new String[]{ARCHIVE_TAG}).targetTags(new String[]{UPLOADER_TAG}));\n        URL url = new java.net.URL(result);\n        HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();\n        ZipInputStream in = new ZipInputStream(new BufferedInputStream(urlConnection.getInputStream()));\n        int files = 0;\n        try {\n            while ((in.getNextEntry()) != null) {\n                files += 1;\n            }\n        } finally {\n            in.close();\n        }\n        assertEquals(2, files);\n    }\n\n    public void testUploadInvalidUrl() {\n        try {\n            cloudinary.uploader().upload(REMOTE_TEST_IMAGE + \"\\n\", asMap(\"return_error\", true));\n            fail(\"Expected exception was not thrown\");\n        } catch (IOException e) {\n            assertEquals(e.getMessage(), \"File not found or unreadable: \" + REMOTE_TEST_IMAGE + \"\\n\");\n        }\n    }\n\n    @Test\n    public void testAccessControl() throws ParseException, IOException {\n        SimpleDateFormat simpleDateFormat = new SimpleDateFormat(\"yyyy-MM-dd HH:mm:ss Z\");\n        final Date start = simpleDateFormat.parse(\"2019-02-22 16:20:57 +0200\");\n        final Date end = simpleDateFormat.parse(\"2019-03-22 00:00:00 +0200\");\n        AccessControlRule acl;\n        AccessControlRule token = AccessControlRule.token();\n\n        acl = AccessControlRule.anonymous(start, null);\n        Map result = cloudinary.uploader().upload(SRC_TEST_IMAGE, asMap(\"access_control\",\n                Arrays.asList(acl, token), \"tags\", Arrays.asList(SDK_TEST_TAG, UPLOADER_TAG)));\n\n        assertNotNull(result);\n        List<Map<String, String>> accessControlResponse = (List<Map<String, String>>) result.get(\"access_control\");\n        assertNotNull(accessControlResponse);\n        assertEquals(2, accessControlResponse.size());\n\n        Map<String, String> acr = accessControlResponse.get(0);\n        assertEquals(\"anonymous\", acr.get(\"access_type\"));\n        assertEquals(\"2019-02-22T14:20:57Z\", acr.get(\"start\"));\n        assertThat(acr, not(hasKey(\"end\")));\n\n        acr = accessControlResponse.get(1);\n        assertEquals(\"token\", acr.get(\"access_type\"));\n        assertThat(acr, not(hasKey(\"start\")));\n        assertThat(acr, not(hasKey(\"end\")));\n\n        result = cloudinary.uploader().upload(SRC_TEST_IMAGE, asMap(\"access_control\",\n                acl, \"tags\", Arrays.asList(SDK_TEST_TAG, UPLOADER_TAG)));\n\n        assertNotNull(result);\n        accessControlResponse = (List<Map<String, String>>) result.get(\"access_control\");\n        assertNotNull(accessControlResponse);\n        acr = accessControlResponse.get(0);\n        assertEquals(1, accessControlResponse.size());\n        assertEquals(\"anonymous\", acr.get(\"access_type\"));\n        assertEquals(\"2019-02-22T14:20:57Z\", acr.get(\"start\"));\n        assertThat(acr, not(hasKey(\"end\")));\n\n        String aclString = \"[{\\\"access_type\\\":\\\"anonymous\\\",\\\"start\\\":\\\"2019-02-22 16:20:57 +0200\\\",\\\"end\\\":\\\"2019-03-22 00:00 +0200\\\"}]\";\n        result = cloudinary.uploader().upload(SRC_TEST_IMAGE, asMap(\"access_control\",\n                aclString, \"tags\", Arrays.asList(SDK_TEST_TAG, UPLOADER_TAG)));\n\n        assertNotNull(result);\n        accessControlResponse = (List<Map<String, String>>) result.get(\"access_control\");\n        assertNotNull(accessControlResponse);\n        assertTrue(accessControlResponse.size() == 1);\n        assertEquals(\"anonymous\", accessControlResponse.get(0).get(\"access_type\"));\n        assertEquals(\"2019-02-22T14:20:57Z\", accessControlResponse.get(0).get(\"start\"));\n        assertEquals(\"2019-03-21T22:00:00Z\", accessControlResponse.get(0).get(\"end\"));\n    }\n\n    @Test\n    public void testOnSuccessScript() throws Exception {\n        String tags = \"[\\\"autocaption\\\"\" + \",\\\"\" + SDK_TEST_TAG + \"\\\",\\\"\" + UPLOADER_TAG + \"\\\"]\";\n        Map result = cloudinary.uploader().upload(SRC_TEST_IMAGE, asMap(\"on_success\", \"current_asset.update({tags:\" + tags + \"});\"));\n        assertTrue(((List<String>)result.get(\"tags\")).contains(\"autocaption\"));\n    }\n\n    @Test\n    public void testQualityAnalysis() throws IOException {\n        Map result = cloudinary.uploader().upload(SRC_TEST_IMAGE, asMap(\"quality_analysis\", true, \"tags\", Arrays.asList(SDK_TEST_TAG, UPLOADER_TAG)));\n        assertNotNull(result.get(\"quality_analysis\"));\n        result = cloudinary.uploader().explicit(result.get(\"public_id\").toString(), ObjectUtils.asMap(\"type\", \"upload\", \"resource_type\", \"image\", \"quality_analysis\", true));\n        assertNotNull(result.get(\"quality_analysis\"));\n\n    }\n\n    @Test\n    public void testCinemagraphAnalysisUpload() throws IOException {\n        Map result = cloudinary.uploader().upload(SRC_TEST_IMAGE, asMap(\"cinemagraph_analysis\", true, \"tags\", Arrays.asList(SDK_TEST_TAG, UPLOADER_TAG)));\n        assertNotNull(result.get(\"cinemagraph_analysis\"));\n        result = cloudinary.uploader().explicit(result.get(\"public_id\").toString(), ObjectUtils.asMap(\"type\", \"upload\", \"resource_type\", \"image\", \"cinemagraph_analysis\", true));\n        assertNotNull(result.get(\"cinemagraph_analysis\"));\n\n    }\n\n    @Test\n    public void testAccessibilityAnalysisUpload() throws IOException {\n        Map result = cloudinary.uploader().upload(SRC_TEST_IMAGE, asMap(\"accessibility_analysis\", true, \"tags\", Arrays.asList(SDK_TEST_TAG, UPLOADER_TAG)));\n        assertNotNull(result.get(\"accessibility_analysis\"));\n        result = cloudinary.uploader().explicit(result.get(\"public_id\").toString(), ObjectUtils.asMap(\"type\", \"upload\", \"resource_type\", \"image\", \"accessibility_analysis\", true));\n        assertNotNull(result.get(\"accessibility_analysis\"));\n    }\n\n    private void addToDeleteList(String type, String id) {\n        Set<String> ids = toDelete.get(type);\n        if (ids == null) {\n            ids = new HashSet<String>();\n            toDelete.put(type, ids);\n        }\n\n        ids.add(id);\n    }\n\n    @Test\n    public void testUploadLocalUnicodeFilename() throws Exception {\n        Map result = cloudinary.uploader().upload(HEBREW_PDF, asMap(\"resource_type\", \"raw\"));\n        assertTrue(((String)result.get(\"public_id\")).contains(\".docx\"));\n    }\n\n    @Test\n    public void testUploadFolderDecoupling() {\n        //TODO: Need to build a unit testing infrastructure\n        Map options = asMap(\n                \"use_filename_as_display_name\", true,\n                \"public_id_prefix\", \"test_id_prefix\",\n                \"asset_folder\", \"asset_folder_test\",\n                \"display_name\", \"display_name_test\",\n                \"use_asset_folder_as_public_id_prefix\", true,\n                \"visual_search\", true);\n\n        Map uploadParams = Util.buildUploadParams(options);\n        Assert.assertEquals(\"test_id_prefix\", uploadParams.get(\"public_id_prefix\"));\n        Assert.assertEquals(true, uploadParams.get(\"use_filename_as_display_name\"));\n        Assert.assertEquals(\"asset_folder_test\", uploadParams.get(\"asset_folder\"));\n        Assert.assertEquals(\"display_name_test\", uploadParams.get(\"display_name\"));\n        Assert.assertEquals(true, uploadParams.get(\"use_asset_folder_as_public_id_prefix\"));\n        Assert.assertEquals(true, uploadParams.get(\"visual_search\"));\n    }\n\n    @Test\n    public void testNotificationUrl() {\n        Map options = asMap(\"notification_url\", \"https://www.test.com\");\n        Map uploadParams = Util.buildUploadParams(options);\n        Assert.assertEquals(\"https://www.test.com\", uploadParams.get(\"notification_url\"));\n    }\n\n    @Test\n    public void testAutoChaptering() throws Exception {\n        Map result = cloudinary.uploader().upload(SRC_TEST_VIDEO,  asMap(\n                \"resource_type\", \"video\", \"auto_chaptering\", true));\n        assert(result != null);\n        assertNotNull(result.get(\"playback_url\"));\n    }\n\n    @Test\n    public void testAutoTranscription() throws Exception {\n        Map result = cloudinary.uploader().upload(SRC_TEST_VIDEO,  asMap(\n                \"resource_type\", \"video\", \"auto_transcription\", true));\n        assert(result != null);\n        assertNotNull(result.get(\"playback_url\"));\n    }\n}\n"
  },
  {
    "path": "cloudinary-test-common/src/main/java/com/cloudinary/test/MetadataTestHelper.java",
    "content": "package com.cloudinary.test;\n\nimport com.cloudinary.Api;\nimport com.cloudinary.api.ApiResponse;\nimport com.cloudinary.metadata.MetadataField;\nimport com.cloudinary.metadata.MetadataValidation;\nimport com.cloudinary.metadata.StringMetadataField;\n\npublic final class MetadataTestHelper {\n    private MetadataTestHelper() {}\n\n    public static StringMetadataField newFieldInstance(String label, Boolean mandatory) throws Exception {\n        StringMetadataField field = new StringMetadataField();\n        field.setLabel(label);\n        field.setMandatory(mandatory);\n        field.setValidation(new MetadataValidation.StringLength(3, 9));\n        field.setDefaultValue(\"val_test\");\n        return field;\n    }\n\n    public static ApiResponse addFieldToAccount(Api api, MetadataField field) throws Exception {\n        ApiResponse apiResponse = api.addMetadataField(field);\n        return apiResponse;\n    }\n}\n\n"
  },
  {
    "path": "cloudinary-test-common/src/main/java/com/cloudinary/test/MockableTest.java",
    "content": "package com.cloudinary.test;\n\nimport com.cloudinary.Cloudinary;\nimport com.cloudinary.test.helpers.Feature;\nimport com.cloudinary.utils.ObjectUtils;\nimport com.cloudinary.utils.StringUtils;\n\nimport static org.junit.Assume.assumeTrue;\n\nimport java.io.IOException;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Random;\n\npublic class MockableTest {\n\n    public static final String HEBREW_PDF = \"../cloudinary-test-common/src/main/resources/אבג.docx\";\n    public static final String SRC_TEST_IMAGE = \"../cloudinary-test-common/src/main/resources/old_logo.png\";\n    public static final String SRC_TEST_VIDEO = \"http://res.cloudinary.com/demo/video/upload/dog.mp4\";\n    public static final String SRC_TEST_RAW = \"../cloudinary-test-common/src/main/resources/docx.docx\";\n    public static final String REMOTE_TEST_IMAGE = \"http://cloudinary.com/images/old_logo.png\";\n    protected static String SUFFIX = StringUtils.isNotBlank(System.getenv(\"TRAVIS_JOB_ID\")) ? System.getenv(\"TRAVIS_JOB_ID\") : String.valueOf(new Random().nextInt(99999));\n    protected static final String SDK_TEST_TAG = \"cloudinary_java_test_\" + SUFFIX;\n    protected Cloudinary cloudinary;\n\n    protected Object getParam(String name){\n        throw new UnsupportedOperationException();\n    }\n    protected String getURL(){\n        throw new UnsupportedOperationException();\n    }\n    protected String getHttpMethod(){\n        throw new UnsupportedOperationException();\n    }\n\n    protected Map preloadResource(Map options) throws IOException {\n        if (!options.containsKey(\"tags\")){\n            throw new IllegalArgumentException(\"Must provide unique per-class tags\");\n        }\n        Map combinedOptions = ObjectUtils.asMap(\"transformation\", \"c_scale,w_100\");\n        combinedOptions.putAll(options);\n        return cloudinary.uploader().upload(\"http://res.cloudinary.com/demo/image/upload/sample\", combinedOptions);\n    }\n\n    private static final List<String> enabledAddons = getEnabledAddons();\n\n    protected void assumeAddonEnabled(String addon) throws Exception {\n        boolean enabled = enabledAddons.contains(addon.toLowerCase()) \n            || (enabledAddons.size() == 1 && enabledAddons.get(0).equalsIgnoreCase(\"all\"));\n\n        assumeTrue(String.format(\"Use CLD_TEST_ADDONS environment variable to enable tests for %s.\", addon), enabled);\n    }\n\n    private static List<String> getEnabledAddons() {\n        String envAddons = System.getenv()\n            .getOrDefault(\"CLD_TEST_ADDONS\", \"\")\n            .toLowerCase()\n            .replaceAll(\"\\\\s\", \"\");\n\n        return Arrays.asList(envAddons.split(\",\"));\n    }\n\n    protected static boolean shouldTestFeature(String feature) {\n        String sdkFeatures = System.getenv()\n                .getOrDefault(\"CLD_TEST_FEATURES\", \"\")\n                .toLowerCase()\n                .replaceAll(\"\\\\s\", \"\");\n        List<String> sdkFeaturesList = Arrays.asList(sdkFeatures.split(\",\"));\n        return sdkFeatures.contains(feature.toLowerCase()) || (sdkFeaturesList.size() == 1 && sdkFeaturesList.get(0).equalsIgnoreCase(Feature.ALL));\n    }\n\n    static protected boolean assumeCloudinaryAccountURLExist() {\n        String cloudinaryAccountUrl = System.getProperty(\"CLOUDINARY_ACCOUNT_URL\", System.getenv(\"CLOUDINARY_ACCOUNT_URL\"));\n        assumeTrue(String.format(\"Use CLOUDINARY_ACCOUNT_URL environment variable to enable tests\"), cloudinaryAccountUrl != null);\n        return cloudinaryAccountUrl != null;\n    }\n}\n"
  },
  {
    "path": "cloudinary-test-common/src/main/java/com/cloudinary/test/TimeoutTest.java",
    "content": "package com.cloudinary.test;\n\n/***\n * Marker interface for Junit categories.\n */\npublic interface TimeoutTest {\n}\n"
  },
  {
    "path": "cloudinary-test-common/src/main/java/com/cloudinary/test/helpers/Feature.java",
    "content": "package com.cloudinary.test.helpers;\n\npublic final class Feature {\n    private Feature() {}\n\n    public static final String ALL = \"all\";\n    public static final String DYNAMIC_FOLDERS = \"dynamic_folders\";\n    public static final String BACKEDUP_ASSETS = \"backedup_assets\";\n    public static final String CONDITIONAL_METADATA_RULES = \"conditional_metadata_rules\";\n}\n"
  },
  {
    "path": "cloudinary-test-common/src/main/java/com/cloudinary/test/rules/RetryRule.java",
    "content": "package com.cloudinary.test.rules;\n\nimport org.junit.rules.TestRule;\nimport org.junit.runner.Description;\nimport org.junit.runners.model.Statement;\n\nimport java.util.Objects;\n\npublic class RetryRule implements TestRule {\n    private int retryCount;\n    private int delay;\n\n    public RetryRule(int retryCount, int delay) {\n        this.retryCount = retryCount;\n        this.delay = delay;\n    }\n\n    public RetryRule() {\n        this.retryCount = 3;\n        this.delay = 3;\n    }\n\n    public Statement apply(Statement base, Description description) {\n        return statement(base, description);\n    }\n\n    private Statement statement(final Statement base, final Description description) {\n        return new Statement() {\n            @Override\n            public void evaluate() throws Throwable {\n                Throwable caughtThrowable = null;\n                for (int i = 0; i < retryCount; i++) {\n                    try {\n                        base.evaluate();\n                        return;\n                    } catch (Throwable t) {\n                        caughtThrowable = t;\n                        System.err.println(description.getDisplayName() + \": run \" + (i + 1) + \" failed.\");\n                        Thread.sleep(delay * 1000);\n                    }\n                }\n                System.err.println(description.getDisplayName() + \": Giving up after \" + retryCount + \" failures.\");\n                throw Objects.requireNonNull(caughtThrowable);\n            }\n        };\n    }\n}\n"
  },
  {
    "path": "gradle/wrapper/gradle-wrapper.properties",
    "content": "distributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-6.0.1-all.zip\n"
  },
  {
    "path": "gradle.properties",
    "content": "publishRepo=https://central.sonatype.com/\nsnapshotRepo=https://central.sonatype.com/\npublishDescription=Cloudinary is a cloud service that offers a solution to a web application's entire image management pipeline. Upload images to the cloud. Automatically perform smart image resizing, cropping and conversion without installing any complex software. Integrate Facebook or Twitter profile image extraction in a snap, in any dimension and style to match your website’s graphics requirements. Images are seamlessly delivered through a fast CDN, and much much more. This Java library allows to easily integrate with Cloudinary in Java applications.\ngithubUrl=http://github.com/cloudinary/cloudinary_java\nscmConnection=scm:git:git://github.com/cloudinary/cloudinary_java.git\nscmDeveloperConnection=scm:git:git@github.com:cloudinary/cloudinary_java.git'\nscmUrl=http://github.com/cloudinary/cloudinary_java\nlicenseName=MIT\nlicenseUrl=http://opensource.org/licenses/MIT\ndeveloperId=cloudinary\ndeveloperName=Cloudinary\ndeveloperEmail=info@cloudinary.com\n\n# These two properties must use these exact names to be compatible with 'gradle install' plugin.\ngroup=com.cloudinary\nversion=2.3.2\n\ngnsp.disableApplyOnlyOnRootProjectEnforcement=true\n\n# see https://github.com/gradle/gradle/issues/11308\nsystemProp.org.gradle.internal.publish.checksums.insecure=true\n\n"
  },
  {
    "path": "gradlew",
    "content": "#!/usr/bin/env sh\n\n##############################################################################\n##\n##  Gradle start up script for UN*X\n##\n##############################################################################\n\n# Attempt to set APP_HOME\n# Resolve links: $0 may be a link\nPRG=\"$0\"\n# Need this for relative symlinks.\nwhile [ -h \"$PRG\" ] ; do\n    ls=`ls -ld \"$PRG\"`\n    link=`expr \"$ls\" : '.*-> \\(.*\\)$'`\n    if expr \"$link\" : '/.*' > /dev/null; then\n        PRG=\"$link\"\n    else\n        PRG=`dirname \"$PRG\"`\"/$link\"\n    fi\ndone\nSAVED=\"`pwd`\"\ncd \"`dirname \\\"$PRG\\\"`/\" >/dev/null\nAPP_HOME=\"`pwd -P`\"\ncd \"$SAVED\" >/dev/null\n\nAPP_NAME=\"Gradle\"\nAPP_BASE_NAME=`basename \"$0\"`\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS=\"\"\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=\"maximum\"\n\nwarn () {\n    echo \"$*\"\n}\n\ndie () {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n}\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\nnonstop=false\ncase \"`uname`\" in\n  CYGWIN* )\n    cygwin=true\n    ;;\n  Darwin* )\n    darwin=true\n    ;;\n  MINGW* )\n    msys=true\n    ;;\n  NONSTOP* )\n    nonstop=true\n    ;;\nesac\n\nCLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=\"$JAVA_HOME/jre/sh/java\"\n    else\n        JAVACMD=\"$JAVA_HOME/bin/java\"\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=\"java\"\n    which java >/dev/null 2>&1 || die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\nfi\n\n# Increase the maximum file descriptors if we can.\nif [ \"$cygwin\" = \"false\" -a \"$darwin\" = \"false\" -a \"$nonstop\" = \"false\" ] ; then\n    MAX_FD_LIMIT=`ulimit -H -n`\n    if [ $? -eq 0 ] ; then\n        if [ \"$MAX_FD\" = \"maximum\" -o \"$MAX_FD\" = \"max\" ] ; then\n            MAX_FD=\"$MAX_FD_LIMIT\"\n        fi\n        ulimit -n $MAX_FD\n        if [ $? -ne 0 ] ; then\n            warn \"Could not set maximum file descriptor limit: $MAX_FD\"\n        fi\n    else\n        warn \"Could not query maximum file descriptor limit: $MAX_FD_LIMIT\"\n    fi\nfi\n\n# For Darwin, add options to specify how the application appears in the dock\nif $darwin; then\n    GRADLE_OPTS=\"$GRADLE_OPTS \\\"-Xdock:name=$APP_NAME\\\" \\\"-Xdock:icon=$APP_HOME/media/gradle.icns\\\"\"\nfi\n\n# For Cygwin, switch paths to Windows format before running java\nif $cygwin ; then\n    APP_HOME=`cygpath --path --mixed \"$APP_HOME\"`\n    CLASSPATH=`cygpath --path --mixed \"$CLASSPATH\"`\n    JAVACMD=`cygpath --unix \"$JAVACMD\"`\n\n    # We build the pattern for arguments to be converted via cygpath\n    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`\n    SEP=\"\"\n    for dir in $ROOTDIRSRAW ; do\n        ROOTDIRS=\"$ROOTDIRS$SEP$dir\"\n        SEP=\"|\"\n    done\n    OURCYGPATTERN=\"(^($ROOTDIRS))\"\n    # Add a user-defined pattern to the cygpath arguments\n    if [ \"$GRADLE_CYGPATTERN\" != \"\" ] ; then\n        OURCYGPATTERN=\"$OURCYGPATTERN|($GRADLE_CYGPATTERN)\"\n    fi\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    i=0\n    for arg in \"$@\" ; do\n        CHECK=`echo \"$arg\"|egrep -c \"$OURCYGPATTERN\" -`\n        CHECK2=`echo \"$arg\"|egrep -c \"^-\"`                                 ### Determine if an option\n\n        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition\n            eval `echo args$i`=`cygpath --path --ignore --mixed \"$arg\"`\n        else\n            eval `echo args$i`=\"\\\"$arg\\\"\"\n        fi\n        i=$((i+1))\n    done\n    case $i in\n        (0) set -- ;;\n        (1) set -- \"$args0\" ;;\n        (2) set -- \"$args0\" \"$args1\" ;;\n        (3) set -- \"$args0\" \"$args1\" \"$args2\" ;;\n        (4) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" ;;\n        (5) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" ;;\n        (6) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" ;;\n        (7) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" ;;\n        (8) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" ;;\n        (9) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" \"$args8\" ;;\n    esac\nfi\n\n# Escape application args\nsave () {\n    for i do printf %s\\\\n \"$i\" | sed \"s/'/'\\\\\\\\''/g;1s/^/'/;\\$s/\\$/' \\\\\\\\/\" ; done\n    echo \" \"\n}\nAPP_ARGS=$(save \"$@\")\n\n# Collect all arguments for the java command, following the shell quoting and substitution rules\neval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS \"\\\"-Dorg.gradle.appname=$APP_BASE_NAME\\\"\" -classpath \"\\\"$CLASSPATH\\\"\" org.gradle.wrapper.GradleWrapperMain \"$APP_ARGS\"\n\n# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong\nif [ \"$(uname)\" = \"Darwin\" ] && [ \"$HOME\" = \"$PWD\" ]; then\n  cd \"$(dirname \"$0\")\"\nfi\n\nexec \"$JAVACMD\" \"$@\"\n"
  },
  {
    "path": "gradlew.bat",
    "content": "@if \"%DEBUG%\" == \"\" @echo off\n@rem ##########################################################################\n@rem\n@rem  Gradle startup script for Windows\n@rem\n@rem ##########################################################################\n\n@rem Set local scope for the variables with windows NT shell\nif \"%OS%\"==\"Windows_NT\" setlocal\n\nset DIRNAME=%~dp0\nif \"%DIRNAME%\" == \"\" set DIRNAME=.\nset APP_BASE_NAME=%~n0\nset APP_HOME=%DIRNAME%\n\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nset DEFAULT_JVM_OPTS=\n\n@rem Find java.exe\nif defined JAVA_HOME goto findJavaFromJavaHome\n\nset JAVA_EXE=java.exe\n%JAVA_EXE% -version >NUL 2>&1\nif \"%ERRORLEVEL%\" == \"0\" goto init\n\necho.\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\necho.\necho Please set the JAVA_HOME variable in your environment to match the\necho location of your Java installation.\n\ngoto fail\n\n:findJavaFromJavaHome\nset JAVA_HOME=%JAVA_HOME:\"=%\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\n\nif exist \"%JAVA_EXE%\" goto init\n\necho.\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%\necho.\necho Please set the JAVA_HOME variable in your environment to match the\necho location of your Java installation.\n\ngoto fail\n\n:init\n@rem Get command-line arguments, handling Windows variants\n\nif not \"%OS%\" == \"Windows_NT\" goto win9xME_args\n\n:win9xME_args\n@rem Slurp the command line arguments.\nset CMD_LINE_ARGS=\nset _SKIP=2\n\n:win9xME_args_slurp\nif \"x%~1\" == \"x\" goto execute\n\nset CMD_LINE_ARGS=%*\n\n:execute\n@rem Setup the command line\n\nset CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\n\n@rem Execute Gradle\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%\n\n:end\n@rem End local scope for the variables with windows NT shell\nif \"%ERRORLEVEL%\"==\"0\" goto mainEnd\n\n:fail\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\nrem the _cmd.exe /c_ return code!\nif  not \"\" == \"%GRADLE_EXIT_CONSOLE%\" exit 1\nexit /b 1\n\n:mainEnd\nif \"%OS%\"==\"Windows_NT\" endlocal\n\n:omega\n"
  },
  {
    "path": "java_shared.gradle",
    "content": "sourceCompatibility = 1.8\ntargetCompatibility = 1.8\n\njavadoc {\n    options.encoding = 'UTF-8'\n}\n\ntest {\n    testLogging.showStandardStreams = true\n    testLogging.exceptionFormat = 'full'\n}\n\ntask sourcesJar(type: Jar, dependsOn: classes) {\n    classifier = 'sources'\n    from sourceSets.main.allSource\n}\n\ntask javadocJar(type: Jar, dependsOn: javadoc) {\n    classifier = 'javadoc'\n    from javadoc.destinationDir\n}\n\nartifacts {\n    archives javadocJar, sourcesJar\n}\n\ntasks.withType(GenerateModuleMetadata) {\n    enabled = false\n}\n\ntasks.withType(Test) {\n    environment 'CLOUDINARY_URL', System.getProperty('CLOUDINARY_URL')\n    maxParallelForks = Runtime.runtime.availableProcessors()\n\n    // show standard out and standard error of the test JVM(s) on the console\n    testLogging.showStandardStreams = true\n}\n\ntasks.withType(JavaCompile) {\n    options.encoding = 'UTF-8'\n}\n"
  },
  {
    "path": "publish.gradle",
    "content": "apply plugin: 'maven-publish'\napply plugin: 'signing'\n\n// Simple module-level publishing for manual upload to Central Portal\nif (hasProperty(\"ossrhTokenPassword\") || hasProperty(\"centralPassword\")) {\n    \n    publishing {\n        publications {\n            mavenJava(MavenPublication) {\n                // Set coordinates from gradle.properties\n                groupId = project.ext.publishGroupId\n                artifactId = project.name\n                version = project.version\n                \n                // Include JAR artifacts and components for Java\n                from components.java\n                artifact sourcesJar\n                artifact javadocJar\n                \n                pom {\n                    name = getModuleName(project.name)\n                    packaging = 'jar'\n                    description = publishDescription\n                    url = githubUrl\n                    \n                    licenses {\n                        license {\n                            name = licenseName\n                            url = licenseUrl\n                        }\n                    }\n                    \n                    developers {\n                        developer {\n                            id = developerId\n                            name = developerName\n                            email = developerEmail\n                        }\n                    }\n                    \n                    scm {\n                        connection = scmConnection\n                        developerConnection = scmDeveloperConnection\n                        url = scmUrl\n                    }\n                }\n            }\n        }\n    }\n    \n    // Signing temporarily disabled - we'll add GPG signatures manually using command line\n    // signing {\n    //     required { project.hasProperty(\"centralPassword\") }\n    //     useGpgCmd()\n    //     sign publishing.publications.mavenJava\n    // }\n}\n\n// Helper function to get proper module names\ndef getModuleName(artifactId) {\n    switch(artifactId) {\n        case 'cloudinary-core':\n            return 'Cloudinary Core Library'\n        case 'cloudinary-http5':\n            return 'Cloudinary Apache HTTP 5 Library'\n        case 'cloudinary-taglib':\n            return 'Cloudinary Taglib Library'\n        case 'cloudinary-test-common':\n            return 'Cloudinary Test Common Library'\n        default:\n            return 'Cloudinary Java Library'\n    }\n}"
  },
  {
    "path": "samples/photo_album/META-INF/persistence.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<persistence xmlns=\"http://java.sun.com/xml/ns/persistence\" version=\"2.0\">\n\n</persistence>"
  },
  {
    "path": "samples/photo_album/README.md",
    "content": "Cloudinary Java/Spring MVC Sample Project\n=========================================\n\nA simple web application that allows you to uploads photos, maintain a database with references to them, list them with their metadata, and display them using various cloud-based transformations.\n\n## Installation\n\nRun the following commands from your shell.\n\nClone the Cloudinary Java project: \n\n    git clone git://github.com/cloudinary/cloudinary_java.git    \n        \nCompile the sample project and create a WAR file (package):\n\n    cd samples/photo_album\n    mvn compile && mvn package\n\nA WAR file should have been created in:\n\n    target/photo_album.war\n\nYou need to deploy this war file into your J2EE container of choice. The following instructions assume a *nix with [Tomcat](http://tomcat.apache.org/) 7 on `$CATALINA_HOME`.\n\n    mv target/photo_album.war $CATALINA_HOME/webapps/\n\nIf you would like to deploy the sample application in the root server path do this instead:\n\n    mv target/photo_album.war $CATALINA_HOME/webapps/ROOT.war\n\n## Configuration\n\nNext you need to pass your Cloudinary account's Cloud Name, API Key, and API Secret. This sample application assumes these values exists in the form\nof a `CLOUDINARY_URL` settings either as an environment variable or as system property. The `CLOUDINARY_URL` value is available in the [dashboard of your Cloudinary account](https://cloudinary.com/console). \nIf you don't have a Cloudinary account yet, [click here](https://cloudinary.com/users/register/free) to create one for free.\n\nThe specific method with which you pass that information to Tomcat (or any other Servlet container) is not important but we present 2 alternatives here.\n\n* Start Tomcat with `CLOUDINARY_URL` as a process bound environment variable\n    \n        CLOUDINARY_URL=cloudinary://<API-KEY>:<API-SECRET>@<CLOUD-NAME> $CATALINA_HOME/bin/startup.sh\n    \n* Set `TOMCAT_OPTS` environment variable either globally (`/etc/profile`) or for the user running Tomcat (`~/.profile`)\n\n        TOMCAT_OPTS=-DCLOUDINARY_URL=cloudinary://<API-KEY>:<API-SECRET>@<CLOUD-NAME>\n\n## Running\n\nIf you chose the second option you will need to now start Tomcat:\n\n    $CATALINA_HOME/bin/startup.sh\n    \nand point your browser to [http://localhost:8080/photo_album/](http://localhost:8080/photo_album/) or [http://localhost:8080/](http://localhost:8080/) if you opted to put the WAR file in root. *Note:* you might need to give Tomcat a while to pick the deployed WAR file explode it, and deploy it.\n\nThis sample uses an in memory [HSQLDB](http://hsqldb.org/) database so you should expect all data to be lost when the servlet process ends."
  },
  {
    "path": "samples/photo_album/pom.xml",
    "content": "<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\r\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd\">\r\n    <modelVersion>4.0.0</modelVersion>\r\n    <groupId>com.cloudinary</groupId>\r\n    <artifactId>photo_album</artifactId>\r\n    <packaging>war</packaging>\r\n    <version>1.0-SNAPSHOT</version>\r\n    <name>photo_album</name>\r\n\r\n    <properties>\r\n        <spring.version>5.3.18</spring.version>\r\n    </properties>\r\n\r\n    <repositories>\r\n        <repository>\r\n            <id>JBoss Repository</id>\r\n            <url>https://repository.jboss.org/nexus/content/repositories/releases</url>\r\n            <name>JBoss Repository</name>\r\n        </repository>\r\n    </repositories>\r\n\r\n    <dependencies>\r\n        <dependency>\r\n            <groupId>com.cloudinary</groupId>\r\n            <artifactId>cloudinary-taglib</artifactId>\r\n            <version>1.14.0</version>\r\n        </dependency>\r\n         <dependency>\r\n            <groupId>com.cloudinary</groupId>\r\n            <artifactId>cloudinary-http44</artifactId>\r\n            <version>1.14.0</version>\r\n        </dependency>\r\n        <dependency>\r\n            <groupId>org.springframework</groupId>\r\n            <artifactId>spring-core</artifactId>\r\n            <version>${spring.version}</version>\r\n        </dependency>\r\n\r\n        <dependency>\r\n            <groupId>org.springframework</groupId>\r\n            <artifactId>spring-web</artifactId>\r\n            <version>${spring.version}</version>\r\n        </dependency>\r\n\r\n        <dependency>\r\n            <groupId>javax.servlet</groupId>\r\n            <artifactId>servlet-api</artifactId>\r\n            <version>2.5</version>\r\n        </dependency>\r\n\r\n        <dependency>\r\n            <groupId>javax.servlet.jsp</groupId>\r\n            <artifactId>jsp-api</artifactId>\r\n            <version>2.1</version>\r\n            <scope>provided</scope>\r\n        </dependency>\r\n\r\n        <dependency>\r\n            <groupId>org.springframework</groupId>\r\n            <artifactId>spring-webmvc</artifactId>\r\n            <version>${spring.version}</version>\r\n        </dependency>\r\n\r\n        <dependency>\r\n            <groupId>org.springframework</groupId>\r\n            <artifactId>spring-test</artifactId>\r\n            <version>${spring.version}</version>\r\n            <scope>test</scope>\r\n        </dependency>\r\n\r\n        <dependency>\r\n            <groupId>junit</groupId>\r\n            <artifactId>junit</artifactId>\r\n            <version>4.12</version>\r\n            <scope>test</scope>\r\n        </dependency>\r\n\r\n        <dependency>\r\n            <groupId>jstl</groupId>\r\n            <artifactId>jstl</artifactId>\r\n            <version>1.2</version>\r\n        </dependency>\r\n\r\n        <dependency>\r\n            <groupId>org.springframework.data</groupId>\r\n            <artifactId>spring-data-jpa</artifactId>\r\n            <version>1.11.20.RELEASE</version>\r\n        </dependency>\r\n\r\n        <dependency>\r\n            <groupId>org.hibernate.javax.persistence</groupId>\r\n            <artifactId>hibernate-jpa-2.0-api</artifactId>\r\n            <version>1.0.1.Final</version>\r\n        </dependency>\r\n\r\n        <dependency>\r\n            <groupId>org.hibernate</groupId>\r\n            <artifactId>hibernate-entitymanager</artifactId>\r\n            <version>5.2.10.Final</version>\r\n        </dependency>\r\n\r\n        <dependency>\r\n            <groupId>org.hsqldb</groupId>\r\n            <artifactId>hsqldb</artifactId>\r\n            <version>2.7.1</version>\r\n        </dependency>\r\n\r\n        <dependency>\r\n            <groupId>commons-fileupload</groupId>\r\n            <artifactId>commons-fileupload</artifactId>\r\n            <version>1.3.3</version>\r\n        </dependency>\r\n\r\n        <dependency>\r\n            <groupId>javax.validation</groupId>\r\n            <artifactId>validation-api</artifactId>\r\n            <version>2.0.0.Final</version>\r\n        </dependency>\r\n\r\n        <dependency>\r\n            <groupId>org.hibernate</groupId>\r\n                <artifactId>hibernate-validator-annotation-processor</artifactId>\r\n            <version>6.0.1.Final</version>\r\n        </dependency>\r\n    </dependencies>\r\n\r\n    <build>\r\n        <finalName>photo_album</finalName>\r\n        <plugins>\r\n            <plugin>\r\n                <artifactId>maven-compiler-plugin</artifactId>\r\n                <configuration>\r\n                    <source>1.6</source>\r\n                    <target>1.6</target>\r\n                </configuration>\r\n            </plugin>\r\n            <plugin>\r\n                <artifactId>maven-surefire-plugin</artifactId>\r\n                <configuration>\r\n                    <includes>\r\n                        <include>**/*Tests.java</include>\r\n                    </includes>\r\n                </configuration>\r\n            </plugin>\r\n        </plugins>\r\n    </build>\r\n</project>\r\n"
  },
  {
    "path": "samples/photo_album/src/main/java/cloudinary/controllers/PhotoController.java",
    "content": "package cloudinary.controllers;\n\nimport cloudinary.lib.PhotoUploadValidator;\nimport cloudinary.models.Photo;\nimport cloudinary.models.PhotoUpload;\nimport cloudinary.repositories.PhotoRepository;\n\nimport com.cloudinary.Cloudinary;\nimport com.cloudinary.utils.ObjectUtils;\nimport com.cloudinary.Singleton;\n\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.stereotype.Controller;\nimport org.springframework.ui.ModelMap;\nimport org.springframework.validation.BindingResult;\nimport org.springframework.web.bind.annotation.*;\n\nimport java.io.IOException;\nimport java.util.Map;\n\n@Controller\n@RequestMapping(\"/\")\npublic class PhotoController {\n    @Autowired\n    private PhotoRepository photoRepository;\n\n    @RequestMapping(value = \"/\", method = RequestMethod.GET)\n    public String listPhotos(ModelMap model) {\n        model.addAttribute(\"photos\", photoRepository.findAll());\n        return \"photos\";\n    }\n\n    @SuppressWarnings(\"rawtypes\")\n\t@RequestMapping(value = \"/upload\", method = RequestMethod.POST)\n    public String uploadPhoto(@ModelAttribute PhotoUpload photoUpload, BindingResult result, ModelMap model) throws IOException {\n        PhotoUploadValidator validator = new PhotoUploadValidator();\n        validator.validate(photoUpload, result);\n\n        Map uploadResult = null;\n        if (photoUpload.getFile() != null && !photoUpload.getFile().isEmpty()) {\n            uploadResult = Singleton.getCloudinary().uploader().upload(photoUpload.getFile().getBytes(),\n                    ObjectUtils.asMap(\"resource_type\", \"auto\"));\n            photoUpload.setPublicId((String) uploadResult.get(\"public_id\"));\n            Object version = uploadResult.get(\"version\");\n            if (version instanceof Integer) {\n                photoUpload.setVersion(new Long((Integer) version));    \n            } else {\n                photoUpload.setVersion((Long) version);\n            }\n            \n            photoUpload.setSignature((String) uploadResult.get(\"signature\"));\n            photoUpload.setFormat((String) uploadResult.get(\"format\"));\n            photoUpload.setResourceType((String) uploadResult.get(\"resource_type\"));\n        }\n\n        if (result.hasErrors()){\n            model.addAttribute(\"photoUpload\", photoUpload);\n            return \"upload_form\";\n        } else {\n            Photo photo = new Photo();\n            photo.setTitle(photoUpload.getTitle());\n            photo.setUpload(photoUpload);\n            model.addAttribute(\"upload\", uploadResult);\n            photoRepository.save(photo);\n            model.addAttribute(\"photo\", photo);\n            return \"upload\";\n        }\n    }\n\n    @RequestMapping(value = \"/upload_form\", method = RequestMethod.GET)\n    public String uploadPhotoForm(ModelMap model) {\n        model.addAttribute(\"photoUpload\", new PhotoUpload());\n        return \"upload_form\";\n    }\n\n    @RequestMapping(value = \"/direct_upload_form\", method = RequestMethod.GET)\n    public String directUploadPhotoForm(ModelMap model) {\n        model.addAttribute(\"photoUpload\", new PhotoUpload());\n        model.addAttribute(\"unsigned\", false);\n        return \"direct_upload_form\";\n    }\n    \n    @SuppressWarnings(\"unchecked\")\n\t@RequestMapping(value = \"/direct_unsigned_upload_form\", method = RequestMethod.GET)\n    public String directUnsignedUploadPhotoForm(ModelMap model) throws Exception {\n        model.addAttribute(\"photoUpload\", new PhotoUpload());\n        model.addAttribute(\"unsigned\", true);\n        Cloudinary cld = Singleton.getCloudinary();\n        String preset = \"sample_\" + cld.apiSignRequest(ObjectUtils.asMap(\"api_key\", cld.config.apiKey), cld.config.apiSecret).substring(0, 10);\n        model.addAttribute(\"preset\", preset);\n        try {\n        \tSingleton.getCloudinary().api().createUploadPreset(ObjectUtils.asMap(\n        \t\t\t\"name\", preset, \n        \t\t\t\"unsigned\", true,\n        \t\t\t\"folder\", \"preset_folder\"));\n        } catch (Exception e) {\n        }\n        return \"direct_upload_form\";\n    }\n}\n"
  },
  {
    "path": "samples/photo_album/src/main/java/cloudinary/lib/PhotoUploadValidator.java",
    "content": "package cloudinary.lib;\n\nimport cloudinary.models.PhotoUpload;\nimport org.springframework.validation.Errors;\nimport org.springframework.validation.ValidationUtils;\nimport org.springframework.validation.Validator;\n\npublic class PhotoUploadValidator implements Validator {\n    public boolean supports(Class clazz) {\n        return PhotoUpload.class.equals(clazz);\n    }\n\n    public void validate(Object obj, Errors e) {\n        ValidationUtils.rejectIfEmpty(e, \"title\", \"title.empty\");\n        PhotoUpload pu = (PhotoUpload) obj;\n        if (pu.getFile() == null || pu.getFile().isEmpty()) {\n            if (!pu.validSignature()) {\n                e.rejectValue(\"signature\", \"signature.mismatch\");\n            }\n        }\n    }\n\n}\n"
  },
  {
    "path": "samples/photo_album/src/main/java/cloudinary/models/Photo.java",
    "content": "package cloudinary.models;\n\nimport com.cloudinary.StoredFile;\n\nimport javax.persistence.*;\nimport java.util.Date;\n\n@Entity(name = \"photos\")\npublic class Photo {\n    @Id\n    @GeneratedValue(strategy = GenerationType.AUTO)\n    private Long id;\n\n    @Basic\n    private String title;\n\n    @Basic\n    private String image;\n\n    @Basic\n    private Date createdAt = new Date();\n\n    public Long getId() {\n        return id;\n    }\n\n    public void setId(Long id) {\n        this.id = id;\n    }\n\n    public String getTitle() {\n        return title;\n    }\n\n    public void setTitle(String title) {\n        this.title = title;\n    }\n\n    public String getImage() {\n        return image;\n    }\n\n    public void setImage(String image) {\n        this.image = image;\n    }\n\n    public Date getCreatedAt() {\n        return createdAt;\n    }\n\n    public void setCreatedAt(Date createdAt) {\n        this.createdAt =  createdAt;\n    }\n\n    public StoredFile getUpload() {\n        StoredFile file = new StoredFile();\n        file.setPreloadedFile(image);\n        return file;\n    }\n\n    public void setUpload(StoredFile file) {\n        this.image = file.getPreloadedFile();\n    }\n\n}"
  },
  {
    "path": "samples/photo_album/src/main/java/cloudinary/models/PhotoUpload.java",
    "content": "package cloudinary.models;\n\nimport com.cloudinary.Singleton;\nimport com.cloudinary.StoredFile;\nimport com.cloudinary.Transformation;\nimport org.springframework.web.multipart.MultipartFile;\n\npublic class PhotoUpload extends StoredFile {\n    private String title;\n\n    private MultipartFile file;\n\n    public String getUrl() {\n        if (version != null && format != null && publicId != null) {\n            return Singleton.getCloudinary().url()\n                    .resourceType(resourceType)\n                    .type(type)\n                    .format(format)\n                    .version(version)\n                    .generate(publicId);\n        } else return null;\n    }\n\n    public String getThumbnailUrl() {\n        if (version != null && format != null && publicId != null) {\n            return Singleton.getCloudinary().url().format(format)\n                    .resourceType(resourceType)\n                    .type(type)\n                    .version(version).transformation(new Transformation().width(150).height(150).crop(\"fit\"))\n                    .generate(publicId);\n        } else return null;\n    }\n\n    public String getComputedSignature() {\n        return getComputedSignature(Singleton.getCloudinary());\n    }\n\n    public boolean validSignature() {\n        return getComputedSignature().equals(signature);\n    }\n\n    public String getTitle() {\n        return title;\n    }\n\n    public void setTitle(String title) {\n        this.title = title;\n    }\n\n    public MultipartFile getFile() {\n        return file;\n    }\n\n    public void setFile(MultipartFile file) {\n        this.file = file;\n    }\n}\n"
  },
  {
    "path": "samples/photo_album/src/main/java/cloudinary/repositories/PhotoRepository.java",
    "content": "package cloudinary.repositories;\n\nimport cloudinary.models.Photo;\nimport org.springframework.data.jpa.repository.JpaRepository;\n\npublic interface PhotoRepository extends JpaRepository<Photo, Long> {\n}"
  },
  {
    "path": "samples/photo_album/src/main/resources/META-INF/persistence.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<persistence version=\"2.0\" xmlns=\"http://java.sun.com/xml/ns/persistence\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd\">\n    <persistence-unit name=\"defaultPersistenceUnit\" transaction-type=\"RESOURCE_LOCAL\">\n        <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>\n        <properties>\n            <property name=\"hibernate.dialect\" value=\"org.hibernate.dialect.HSQLDialect\" />\n            <property name=\"hibernate.connection.url\" value=\"jdbc:hsqldb:mem:spring\" />\n            <property name=\"hibernate.connection.driver_class\" value=\"org.hsqldb.jdbcDriver\" />\n            <property name=\"hibernate.connection.username\" value=\"sa\" />\n            <property name=\"hibernate.connection.password\" value=\"\" />\n            <property name=\"hibernate.hbm2ddl.auto\" value=\"create-drop\" />\n        </properties>\n    </persistence-unit>\n</persistence>"
  },
  {
    "path": "samples/photo_album/src/main/webapp/WEB-INF/messages.properties",
    "content": "title.empty = Title cannot be empty\nsignature.mismatch = Signature mismatch"
  },
  {
    "path": "samples/photo_album/src/main/webapp/WEB-INF/mvc-dispatcher-servlet.xml",
    "content": "<beans xmlns=\"http://www.springframework.org/schema/beans\"\r\n       xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\r\n       xmlns:context=\"http://www.springframework.org/schema/context\"\r\n       xmlns:jpa=\"http://www.springframework.org/schema/data/jpa\"\r\n       xmlns:mvc=\"http://www.springframework.org/schema/mvc\"\r\n       xsi:schemaLocation=\"http://www.springframework.org/schema/beans\r\n        http://www.springframework.org/schema/beans/spring-beans.xsd\r\n        http://www.springframework.org/schema/context\r\n        http://www.springframework.org/schema/context/spring-context.xsd\r\n        http://www.springframework.org/schema/mvc\r\n        http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd\r\n        http://www.springframework.org/schema/data/jpa\r\n        http://www.springframework.org/schema/data/jpa/spring-jpa.xsd\">\r\n\r\n    <context:component-scan base-package=\"cloudinary\"/>\r\n\r\n    <bean class=\"org.springframework.web.servlet.view.InternalResourceViewResolver\">\r\n        <property name=\"prefix\" value=\"/WEB-INF/pages/\"/>\r\n        <property name=\"suffix\" value=\".jsp\"/>\r\n    </bean>\r\n\r\n    <bean id=\"entityManagerFactory\" class=\"org.springframework.orm.jpa.LocalEntityManagerFactoryBean\">\r\n        <property name=\"persistenceUnitName\" value=\"defaultPersistenceUnit\"/>\r\n    </bean>\r\n\r\n    <bean id=\"transactionManager\" class=\"org.springframework.orm.jpa.JpaTransactionManager\">\r\n        <property name=\"entityManagerFactory\" ref=\"entityManagerFactory\" />\r\n    </bean>\r\n\r\n    <bean id=\"multipartResolver\"\r\n          class=\"org.springframework.web.multipart.commons.CommonsMultipartResolver\">\r\n\r\n        <!-- one of the properties available; the maximum file size in bytes -->\r\n        <property name=\"maxUploadSize\" value=\"10000000\"/>\r\n    </bean>\r\n\r\n    <bean id=\"messageSource\"\r\n          class=\"org.springframework.context.support.ReloadableResourceBundleMessageSource\">\r\n        <property name=\"basename\" value=\"/WEB-INF/messages\"/>\r\n    </bean>\r\n\r\n    <mvc:annotation-driven />\r\n\r\n    <jpa:repositories base-package=\"cloudinary.repositories\" />\r\n\r\n    <mvc:default-servlet-handler/>\r\n\r\n    <mvc:resources mapping=\"/javascripts/*.js\" location=\"/assets/javascripts/\" />\r\n    <mvc:resources mapping=\"/javascripts/cloudinary/*.js\" location=\"/assets/javascripts/cloudinary/\" />\r\n    <mvc:resources mapping=\"/stylesheets/*.css\" location=\"/assets/stylesheets/\" />\r\n    <mvc:resources mapping=\"/cloudinary_cors.html\" location=\"/assets/\" />\r\n</beans>"
  },
  {
    "path": "samples/photo_album/src/main/webapp/WEB-INF/pages/direct_upload_form.jsp",
    "content": "<!doctype html>\n<%@taglib uri=\"http://www.springframework.org/tags\" prefix=\"spring\" %>\n<%@taglib uri=\"http://www.springframework.org/tags/form\" prefix=\"form\" %>\n<%@taglib uri=\"http://java.sun.com/jsp/jstl/core\" prefix=\"c\" %>\n\n<%@include file=\"pre.jsp\"%>\n\n<div id=\"direct_upload\">\n    <h1>New Photo</h1>\n    <h2>Direct upload from the browser</h2>\n    <p>You can also drag and drop an image file into the dashed area.</p>\n    <form:form method=\"post\" action=\"upload\" commandName=\"photoUpload\" enctype=\"multipart/form-data\">\n        <div class=\"form_line\">\n            <form:label path=\"title\">Title:</form:label>\n            <div class=\"form_controls\">\n                <form:input path=\"title\"/>\n                <form:errors path=\"title\" extraClasses=\"error\" />\n            </div>\n        </div>\n        <div class=\"form_line\">\n            <label>Image:</label>\n            <div class=\"form_controls\">\n                <div class=\"upload_button_holder\">\n                    <a href=\"#\" class=\"upload_button\">Upload</a>\n\t\t    <c:if test=\"${unsigned}\">\n                      <cl:unsignedUpload fieldName=\"preloadedFile\" uploadPreset=\"${preset}\" extraClasses=\"extra\"/>\n\t\t    </c:if>\n\t\t    <c:if test=\"${!unsigned}\">\n\t\t      <cl:upload fieldName=\"preloadedFile\" transformation=\"w_1000,h_1000,c_limit\"\n                            eager=\"c_scale,w_150,h_150|c_fit,w_150,h_150\" extraClasses=\"extra\" exif=\"true\"\n                            imageMetadata=\"true\" colors=\"true\" faces=\"true\"/>\n\t\t    </c:if>\n                </div>\n                <span class=\"status\"></span>\n            </div>\n        </div>\n        <div class=\"form_line\">\n            <div class=\"form_controls\">\n                <div class=\"preview\"></div>\n            </div>\n        </div>\n        <div class=\"form_line\">\n            <div class=\"form_controls\">\n                <input type=\"submit\" value=\"Submit Photo\">\n            <form:errors path=\"signature\" extraClasses=\"error\" />\n            </div>\n        </div>\n    </form:form>\n</div>\n\n<a href=\"<c:url value=\"/\"/>\" class=\"back_link\">Back to list</a>\n\n<div id=\"info\"></div>\n\n<cl:jsinclude/>\n\n<!-- Configure Cloudinary jQuery plugin -->\n<cl:jsconfig/>\n\n<script type=\"text/javascript\">\n    $(document).ready(function() {\n        // Cloudinary jQuery integration library uses jQuery File Upload widget\n        // (see http://blueimp.github.io/jQuery-File-Upload/).\n        // Any file input field with cloudinary-fileupload class is automatically\n        // wrapped using the File Upload widget and configured for Cloudinary uploads.\n        // You can further customize the configuration using .fileupload method\n        // as we do below.\n        $(\".cloudinary-fileupload\")\n                .fileupload({\n                    // Uncomment the following lines to enable client side image resizing and valiation.\n                    // Make sure cloudinary/processing is included the js file\n                    //disableImageResize: false,\n                    //imageMaxWidth: 800,\n                    //imageMaxHeight: 600,\n                    //acceptFileTypes: /(\\.|\\/)(gif|jpe?g|png|bmp|ico)$/i,\n                    //maxFileSize: 20000000, // 20MB\n                    dropZone: \"#direct_upload\",\n                    start: function (e) {\n                        $(\".status\").text(\"Starting upload...\");\n                    },\n                    progress: function (e, data) {\n                        $(\".status\").text(\"Uploading... \" + Math.round((data.loaded * 100.0) / data.total) + \"%\");\n                    },\n                    fail: function (e, data) {\n                        $(\".status\").text(\"Upload failed\");\n                    }\n                })\n                .off(\"cloudinarydone\").on(\"cloudinarydone\", function (e, data) {\n                    $(\"#photo_bytes\").val(data.result.bytes);\n                    $(\".status\").text(\"\");\n                    if (data.result.resource_type == \"image\") {\n                        $(\".preview\").html(\n                                $.cloudinary.image(data.result.public_id, {\n                                    format: data.result.format, width: 50, height: 50, crop: \"fit\"\n                                })\n                        );\n                    }\n                    view_upload_details(data.result);\n                });\n    });\n\n    function view_upload_details(upload) {\n        // Build an html table out of the upload object\n        var rows = [];\n        $.each(upload, function(k,v){\n            rows.push(\n                    $(\"<tr>\")\n                            .append($(\"<td>\").text(k))\n                            .append($(\"<td>\").text(JSON.stringify(v))));\n        });\n        $(\"#info\").html(\n                $(\"<div class=\\\"upload_details\\\">\")\n                        .append(\"<h2>Upload metadata:</h2>\")\n                        .append($(\"<table>\").append(rows)));\n    }\n</script>\n\n<%@include file=\"post.jsp\"%>\n"
  },
  {
    "path": "samples/photo_album/src/main/webapp/WEB-INF/pages/photos.jsp",
    "content": "<!doctype html>\r\n<%@include file=\"pre.jsp\"%>\r\n<%@taglib uri=\"http://www.springframework.org/tags\" prefix=\"spring\" %>\r\n<%@taglib uri=\"http://www.springframework.org/tags/form\" prefix=\"form\" %>\r\n<%@taglib uri=\"http://java.sun.com/jsp/jstl/core\" prefix=\"c\" %>\r\n\r\n<div id=\"posterframe\">\r\n<!-- This will render the fetched Facebook profile picture using Cloudinary according to the\r\nrequested transformations. This also shows how to chain transformations -->\r\n<cl:image src=\"officialchucknorrispage\" type=\"facebook\" format=\"png\" height=\"95\" width=\"95\" crop=\"thumb\" gravity=\"face\" effect=\"sepia\" radius=\"20\">\r\n    <jsp:attribute name=\"transformation\">\r\n        <cl:transformation angle=\"10\"/>\r\n    </jsp:attribute>\r\n</cl:image>\r\n</div>\r\n\r\n<h1>Welcome!</h1>\r\n\r\n<p>\r\n    This is the main demo page of the PhotoAlbum sample Spring MVC application of Cloudinary.<br />\r\n    Here you can see all images you have uploaded to this Spring application and find some information on how\r\n    to implement your own Spring application storing, manipulating and serving your photos using Cloudinary!\r\n</p>\r\n\r\n<p>\r\n    All of the images you see here are transformed and served by Cloudinary.\r\n    For instance, the logo and the poster frame.\r\n    They are both generated in the cloud using the Cloudinary shortcut functions: fetch_image_tag and facebook_profile_image_tag.\r\n    These two pictures weren't even have to be uploaded to Cloudinary, they are retrieved by the service, transformed, cached and distributed through a CDN.\r\n</p>\r\n\r\n<h1>Your Photos</h1>\r\n\r\n<div class=\"actions\">\r\n    <a class=\"upload_link\" href=\"upload_form\">Add photo</a>\r\n    <a class=\"upload_link\" href=\"direct_upload_form\">Add photo (direct upload)</a>\r\n    <a class=\"upload_link\" href=\"direct_unsigned_upload_form\">Add photo (direct unsigned upload)</a>\r\n</div>\r\n\r\n<div class=\"photos\">\r\n    <c:if test=\"${empty photos}\">\r\n        <p>No photos were added yet.</p>\r\n    </c:if>\r\n\r\n    <c:if test=\"${!empty photos}\">\r\n        <c:forEach items=\"${photos}\" var=\"photo\">\r\n            <div class=\"photo\">\r\n                <h2>${photo.title}</h2>\r\n                <c:if test=\"${photo.upload.isImage}\">\r\n                    <a href=\"<cl:url storedSrc=\"${photo.upload}\" format=\"jpg\"/>\" target=\"_blank\">\r\n                        <cl:image storedSrc=\"${photo.upload}\" extraClasses=\"thumbnail inline\" width=\"150\" height=\"150\" crop=\"fit\" quality=\"80\" format=\"jpg\"/>\r\n                    </a>\r\n\r\n                    <div class=\"less_info\">\r\n                        <a href=\"#\" class=\"toggle_info\">Show transformations</a>\r\n                    </div>\r\n\r\n                    <div class=\"more_info\">\r\n                        <a href=\"#\" class=\"toggle_info\">Hide transformations</a>\r\n                        <table class=\"thumbnails\">\r\n                            <td>\r\n                                <div class=\"thumbnail_holder\">\r\n                                    <cl:image storedSrc=\"${photo.upload}\" extraClasses=\"thumbnail inline\" crop=\"fill\" height=\"150\" width=\"150\" radius=\"10\" format=\"jpg\"/>\r\n                                </div>\r\n                                <table class=\"info\">\r\n                                    <tr><td>crop</td><td>fill</td></tr>\r\n                                    <tr><td>width</td><td>150</td></tr>\r\n                                    <tr><td>height</td><td>150</td></tr>\r\n                                    <tr><td>radius</td><td>10</td></tr>\r\n                                </table>\r\n                                <br/>\r\n                            </td>\r\n                            <td>\r\n                                <div class=\"thumbnail_holder\">\r\n                                    <cl:image storedSrc=\"${photo.upload}\" extraClasses=\"thumbnail inline\" crop=\"scale\" height=\"150\" width=\"150\" format=\"jpg\"/>\r\n                                </div>\r\n                                <table class=\"info\">\r\n                                    <tr><td>crop</td><td>scale</td></tr>\r\n                                    <tr><td>width</td><td>150</td></tr>\r\n                                    <tr><td>height</td><td>150</td></tr>\r\n                                </table>\r\n                                <br/>\r\n                            </td>\r\n                            <td>\r\n                                <div class=\"thumbnail_holder\">\r\n                                    <cl:image storedSrc=\"${photo.upload}\" extraClasses=\"thumbnail inline\" crop=\"fit\" height=\"150\" width=\"150\" format=\"jpg\"/>\r\n                                </div>\r\n                                <table class=\"info\">\r\n                                    <tr><td>crop</td><td>fit</td></tr>\r\n                                    <tr><td>width</td><td>150</td></tr>\r\n                                    <tr><td>height</td><td>150</td></tr>\r\n                                </table>\r\n                                <br/>\r\n                            </td>\r\n                            <td>\r\n                                <div class=\"thumbnail_holder\">\r\n                                    <cl:image storedSrc=\"${photo.upload}\" extraClasses=\"thumbnail inline\" crop=\"thumb\" gravity=\"face\" height=\"150\" width=\"150\" format=\"jpg\"/>\r\n                                </div>\r\n                                <table class=\"info\">\r\n                                    <tr><td>crop</td><td>thumb</td></tr>\r\n                                    <tr><td>gravity</td><td>face</td></tr>\r\n                                    <tr><td>width</td><td>150</td></tr>\r\n                                    <tr><td>height</td><td>150</td></tr>\r\n                                </table>\r\n                                <br/>\r\n                            </td>\r\n                            <td>\r\n                                <div class=\"thumbnail_holder\">\r\n                                    <cl:image storedSrc=\"${photo.upload}\" extraClasses=\"thumbnail inline\" format=\"png\" angle=\"20\">\r\n                                        <jsp:attribute name=\"transformation\">\r\n                                            <cl:transformation crop=\"fill\" gravity=\"north\" height=\"150\" width=\"150\" effect=\"sepia\"/>\r\n                                        </jsp:attribute>\r\n                                    </cl:image>\r\n                                </div>\r\n                                <table class=\"info\">\r\n                                    <tr><td>format</td><td>png</td></tr>\r\n                                    <tr><td>angle</td><td>20</td></tr>\r\n                                    <tr><td colspan=\"2\">and then</td></tr>\r\n                                    <tr><td>crop</td><td>fill</td></tr>\r\n                                    <tr><td>gravity</td><td>north</td></tr>\r\n                                    <tr><td>effect</td><td>sepia</td></tr>\r\n                                    <tr><td>width</td><td>150</td></tr>\r\n                                    <tr><td>height</td><td>150</td></tr>\r\n                                </table>\r\n                                <br/>\r\n                            </td>\r\n                            <td>\r\n                                <div class=\"thumbnail_holder\">\r\n                                    <cl:image storedSrc=\"${photo.upload}\" extraClasses=\"thumbnail inline\" crop=\"crop\" gravity=\"face\" height=\"150\" width=\"150\" dpr=\"auto\" format=\"jpg\"/>\r\n                                </div>\r\n                                <table class=\"info\">\r\n                                    <tr><td>crop</td><td>crop</td></tr>\r\n                                    <tr><td>gravity</td><td>face</td></tr>\r\n                                    <tr><td>width</td><td>150</td></tr>\r\n                                    <tr><td>height</td><td>150</td></tr>\r\n                                    <tr><td>dpr</td><td>auto</td></tr>\r\n                                </table>\r\n                                <br/>\r\n                            </td>\r\n                        </table>\r\n\r\n                        <div class=\"note\">\r\n                            Take a look at our documentation of <a href=\"http://cloudinary.com/documentation/image_transformations\" target=\"_blank\">Image Transformations</a> for a full list of supported transformations.\r\n                        </div>\r\n                    </div>\r\n                </c:if>\r\n                <c:if test=\"${!photo.upload.isImage}\">\r\n                    <c:if test=\"${photo.upload.isVideo}\">\r\n                        <a href=\"<cl:url storedSrc=\"${photo.upload}\"/>\" target=\"_blank\">Open in new Tab</a>\r\n                        <cl:video storedSrc=\"${photo.upload}\" extraClasses=\"thumbnail inline\" width=\"150\" height=\"150\" crop=\"fit\" quality=\"80\" mp4Transformation=\"q_60\" ogvTransformation=\"q_70\" owebTransformation=\"q_65\" controls=\"true\"/>\r\n                    </c:if>\r\n                    <c:if test=\"${!photo.upload.isVideo}\">\r\n                        <a href=\"<cl:url storedSrc=\"${photo.upload}\"/>\" target=\"_blank\">Non Image File</a>\r\n                    </c:if>\r\n                </c:if>\r\n            </div>\r\n        </c:forEach>\r\n    </c:if>\r\n</div>\r\n<cl:jsinclude/>\r\n<script type='text/javascript'>\r\n    $('.toggle_info').click(function () {\r\n        $(this).closest('.photo').toggleClass('show_more_info');\r\n        return false;\r\n    });\r\n    $.cloudinary.responsive();\r\n</script>\r\n<%@include file=\"post.jsp\"%>\r\n"
  },
  {
    "path": "samples/photo_album/src/main/webapp/WEB-INF/pages/post.jsp",
    "content": "</div>\n</body>\n</html>\n"
  },
  {
    "path": "samples/photo_album/src/main/webapp/WEB-INF/pages/pre.jsp",
    "content": "<%@taglib uri=\"http://java.sun.com/jsp/jstl/core\" prefix=\"c\" %>\n<%@taglib uri=\"http://cloudinary.com/jsp/taglib\" prefix=\"cl\" %>\n<html>\n<head>\n    <title>Photo Album</title>\n    <link type=\"text/css\" rel=\"stylesheet\" media=\"all\" href=\"<c:url value=\"/stylesheets/application.css\"/>\">\n    <link rel=\"shortcut icon\"\n          href=\"<cl:url src=\"http://cloudinary.com/favicon.png\" type=\"fetch\" effect=\"sepia\"/>\" />\n    <script src=\"http://code.jquery.com/jquery-1.10.1.min.js\"></script>\n</head>\n<body>\n\n<div id=\"logo\">\n    <!-- This will render the image fetched from a remote HTTP URL using Cloudinary -->\n    <cl:image src=\"http://cloudinary.com/images/logo.png\" type=\"fetch\" secure=\"true\" signed=\"true\" />\n</div>\n\n<div class=\"content\">"
  },
  {
    "path": "samples/photo_album/src/main/webapp/WEB-INF/pages/upload.jsp",
    "content": "<!doctype html>\n<%@taglib uri=\"http://www.springframework.org/tags\" prefix=\"spring\" %>\n<%@taglib uri=\"http://www.springframework.org/tags/form\" prefix=\"form\" %>\n<%@taglib uri=\"http://java.sun.com/jsp/jstl/core\" prefix=\"c\" %>\n\n<%@include file=\"pre.jsp\"%>\n\n<h1>Your photo was uploaded sucessfully!</h1>\n\n<c:if test=\"${!empty photo}\">\n<div class=\"photo\">\n    <h2>${photo.title}</h2>\n    <a href=\"<cl:url storedSrc=\"${photo.upload}\"/>\" target=\"_blank\">\n        <c:if test=\"${photoUpload.isImage}\">\n            <cl:image storedSrc=\"${photo.upload}\" extraClasses=\"thumbnail inline\" />\n        </c:if>\n        <c:if test=\"${!photoUpload.isImage}\">\n            Non image file\n        </c:if>\n    </a>\n</div>\n</c:if>\n\n<a href=\"<c:url value=\"/\"/>\" class=\"back_link\">Back to list</a>\n\n<c:if test=\"${!empty upload}\">\n<div class=\"upload_details\">\n    <h2>Upload metadata:</h2>\n    <table>\n        <c:forEach var=\"entry\" items=\"${upload}\">\n            <tr><td>${entry.key}</td><td>${entry.value}</td></tr>\n        </c:forEach>\n    </table>\n</div>\n</c:if>\n\n<%@include file=\"post.jsp\"%>"
  },
  {
    "path": "samples/photo_album/src/main/webapp/WEB-INF/pages/upload_form.jsp",
    "content": "<!doctype html>\n<%@taglib uri=\"http://www.springframework.org/tags\" prefix=\"spring\" %>\n<%@taglib uri=\"http://www.springframework.org/tags/form\" prefix=\"form\" %>\n<%@taglib uri=\"http://java.sun.com/jsp/jstl/core\" prefix=\"c\" %>\n\n<%@include file=\"pre.jsp\"%>\n<!-- A standard form for uploading images to your server -->\n<div id='backend_upload'>\n    <h1>New Photo</h1>\n    <h2>Image file is uploaded through the server</h2>\n    <form:form method=\"post\" action=\"upload\" commandName=\"photoUpload\" enctype=\"multipart/form-data\">\n        <div class=\"form_line\">\n            <form:label path=\"title\">Title:</form:label>\n            <div class=\"form_controls\">\n                <form:input path=\"title\"/>\n                <form:errors path=\"title\" extraClasses=\"error\" />\n            </div>\n        </div>\n\n        <c:if test=\"${empty photoUpload.publicId}\">\n            <div class=\"form_line\">\n                <label for=\"file\">Image:</label>\n                <div class=\"form_controls\">\n                    <input type=\"file\" name=\"file\" id=\"file\"/>\n                </div>\n            </div>\n        </c:if>\n        <c:if test=\"${!empty photoUpload.publicId}\">\n            <c:if test=\"${photoUpload.isImage}\">\n                <div class=\"form_line\">\n                    <label>Image:</label>\n                    <div class=\"form_controls\">\n                        <img src=\"${photoUpload.thumbnailUrl}\"/>\n                    </div>\n                </div>\n            </c:if>\n            <c:if test=\"${!photoUpload.isImage}\">\n                <div class=\"form_line\">\n                    <label>Raw file:</label>\n                    <div class=\"form_controls\">\n                        <a href=\"<cl:url storedSrc=\"${photoUpload}\"/>\">${photoUpload.publicId}</a>\n                    </div>\n                </div>\n            </c:if>\n        </c:if>\n        <div class=\"form_line\">\n            <div class=\"form_controls\">\n                <input type=\"submit\" value=\"Submit Photo\"/>\n            </div>\n        </div>\n        <form:hidden path=\"preloadedFile\"/>\n        <form:errors path=\"signature\" extraClasses=\"error\" />\n    </form:form>\n\n</div>\n\n<a href=\"<c:url value=\"/\"/>\" class=\"back_link\">Back to list</a>\n<%@include file=\"post.jsp\"%>\n\n"
  },
  {
    "path": "samples/photo_album/src/main/webapp/WEB-INF/web.xml",
    "content": "<web-app version=\"2.4\"\r\n         xmlns=\"http://java.sun.com/xml/ns/j2ee\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\r\n         xsi:schemaLocation=\"http://java.sun.com/xml/ns/j2ee\r\n\thttp://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd\">\r\n\r\n\t<display-name>Spring MVC Application</display-name>\r\n\r\n    <servlet>\r\n\t\t<servlet-name>mvc-dispatcher</servlet-name>\r\n\t\t<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>\r\n        <load-on-startup>1</load-on-startup>\r\n\t</servlet>\r\n\r\n\t<servlet-mapping>\r\n\t\t<servlet-name>mvc-dispatcher</servlet-name>\r\n\t\t<url-pattern>/</url-pattern>\r\n\t</servlet-mapping>\r\n</web-app>"
  },
  {
    "path": "samples/photo_album/src/main/webapp/assets/cloudinary_cors.html",
    "content": "<!DOCTYPE HTML>\n<html>\n<head>\n  <meta charset=\"utf-8\">\n</head>\n<body>\n  <script>\n/*\n    json2.js\n    2011-10-19\n\n    Public Domain.\n\n    NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.\n\n    See http://www.JSON.org/js.html\n\n    This code should be minified before deployment.\n    See http://javascript.crockford.com/jsmin.html\n\n    USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO\n    NOT CONTROL.\n\n*/\nvar JSON;if(!JSON){JSON={}}(function(){function str(a,b){var c,d,e,f,g=gap,h,i=b[a];if(i&&typeof i===\"object\"&&typeof i.toJSON===\"function\"){i=i.toJSON(a)}if(typeof rep===\"function\"){i=rep.call(b,a,i)}switch(typeof i){case\"string\":return quote(i);case\"number\":return isFinite(i)?String(i):\"null\";case\"boolean\":case\"null\":return String(i);case\"object\":if(!i){return\"null\"}gap+=indent;h=[];if(Object.prototype.toString.apply(i)===\"[object Array]\"){f=i.length;for(c=0;c<f;c+=1){h[c]=str(c,i)||\"null\"}e=h.length===0?\"[]\":gap?\"[\\n\"+gap+h.join(\",\\n\"+gap)+\"\\n\"+g+\"]\":\"[\"+h.join(\",\")+\"]\";gap=g;return e}if(rep&&typeof rep===\"object\"){f=rep.length;for(c=0;c<f;c+=1){if(typeof rep[c]===\"string\"){d=rep[c];e=str(d,i);if(e){h.push(quote(d)+(gap?\": \":\":\")+e)}}}}else{for(d in i){if(Object.prototype.hasOwnProperty.call(i,d)){e=str(d,i);if(e){h.push(quote(d)+(gap?\": \":\":\")+e)}}}}e=h.length===0?\"{}\":gap?\"{\\n\"+gap+h.join(\",\\n\"+gap)+\"\\n\"+g+\"}\":\"{\"+h.join(\",\")+\"}\";gap=g;return e}}function quote(a){escapable.lastIndex=0;return escapable.test(a)?'\"'+a.replace(escapable,function(a){var b=meta[a];return typeof b===\"string\"?b:\"\\\\u\"+(\"0000\"+a.charCodeAt(0).toString(16)).slice(-4)})+'\"':'\"'+a+'\"'}function f(a){return a<10?\"0\"+a:a}\"use strict\";if(typeof Date.prototype.toJSON!==\"function\"){Date.prototype.toJSON=function(a){return isFinite(this.valueOf())?this.getUTCFullYear()+\"-\"+f(this.getUTCMonth()+1)+\"-\"+f(this.getUTCDate())+\"T\"+f(this.getUTCHours())+\":\"+f(this.getUTCMinutes())+\":\"+f(this.getUTCSeconds())+\"Z\":null};String.prototype.toJSON=Number.prototype.toJSON=Boolean.prototype.toJSON=function(a){return this.valueOf()}}var cx=/[\\u0000\\u00ad\\u0600-\\u0604\\u070f\\u17b4\\u17b5\\u200c-\\u200f\\u2028-\\u202f\\u2060-\\u206f\\ufeff\\ufff0-\\uffff]/g,escapable=/[\\\\\\\"\\x00-\\x1f\\x7f-\\x9f\\u00ad\\u0600-\\u0604\\u070f\\u17b4\\u17b5\\u200c-\\u200f\\u2028-\\u202f\\u2060-\\u206f\\ufeff\\ufff0-\\uffff]/g,gap,indent,meta={\"\\b\":\"\\\\b\",\"\\t\":\"\\\\t\",\"\\n\":\"\\\\n\",\"\\f\":\"\\\\f\",\"\\r\":\"\\\\r\",'\"':'\\\\\"',\"\\\\\":\"\\\\\\\\\"},rep;if(typeof JSON.stringify!==\"function\"){JSON.stringify=function(a,b,c){var d;gap=\"\";indent=\"\";if(typeof c===\"number\"){for(d=0;d<c;d+=1){indent+=\" \"}}else if(typeof c===\"string\"){indent=c}rep=b;if(b&&typeof b!==\"function\"&&(typeof b!==\"object\"||typeof b.length!==\"number\")){throw new Error(\"JSON.stringify\")}return str(\"\",{\"\":a})}}if(typeof JSON.parse!==\"function\"){JSON.parse=function(text,reviver){function walk(a,b){var c,d,e=a[b];if(e&&typeof e===\"object\"){for(c in e){if(Object.prototype.hasOwnProperty.call(e,c)){d=walk(e,c);if(d!==undefined){e[c]=d}else{delete e[c]}}}}return reviver.call(a,b,e)}var j;text=String(text);cx.lastIndex=0;if(cx.test(text)){text=text.replace(cx,function(a){return\"\\\\u\"+(\"0000\"+a.charCodeAt(0).toString(16)).slice(-4)})}if(/^[\\],:{}\\s]*$/.test(text.replace(/\\\\(?:[\"\\\\\\/bfnrt]|u[0-9a-fA-F]{4})/g,\"@\").replace(/\"[^\"\\\\\\n\\r]*\"|true|false|null|-?\\d+(?:\\.\\d*)?(?:[eE][+\\-]?\\d+)?/g,\"]\").replace(/(?:^|:|,)(?:\\s*\\[)+/g,\"\"))){j=eval(\"(\"+text+\")\");return typeof reviver===\"function\"?walk({\"\":j},\"\"):j}throw new SyntaxError(\"JSON.parse\")}}})()\n/* end of json2.js */\n\n    function parse(query) {\n      var result = {};\n      var params = query.split(\"&\");\n      for (var i = 0; i < params.length; i++) {\n        var param = params[i].split(\"=\");\n        result[param[0]] = decodeURIComponent(param[1]);\n      }\n      return JSON.stringify(result);\n    }\n    document.body.innerHTML=parse(window.location.search.slice(1));\n  </script>\n</body>\n</html>\n\n"
  },
  {
    "path": "samples/photo_album/src/main/webapp/assets/javascripts/cloudinary/jquery.cloudinary.js",
    "content": "/*\r\n * Cloudinary's jQuery library - v1.0.19\r\n * Copyright Cloudinary\r\n * see https://github.com/cloudinary/cloudinary_js\r\n */\r\n\r\n(function (factory) {\r\n  'use strict';\r\n  if (typeof define === 'function' && define.amd) {\r\n    // Register as an anonymous AMD module:\r\n    define([\r\n      'jquery',\r\n      'jquery.ui.widget',\r\n      'jquery.iframe-transport',\r\n      'jquery.fileupload'\r\n    ], factory);\r\n  } else {\r\n    // Browser globals:\r\n    var $ = window.jQuery;\r\n    factory($);\r\n    $(function() {\r\n      if($.fn.cloudinary_fileupload !== undefined) {\r\n        $(\"input.cloudinary-fileupload[type=file]\").cloudinary_fileupload();\r\n      }\r\n    });\r\n  }\r\n}(function ($) {\r\n  'use strict';\r\n  var CF_SHARED_CDN = \"d3jpl91pxevbkh.cloudfront.net\";\r\n  var OLD_AKAMAI_SHARED_CDN = \"cloudinary-a.akamaihd.net\";\r\n  var AKAMAI_SHARED_CDN = \"res.cloudinary.com\";\r\n  var SHARED_CDN = AKAMAI_SHARED_CDN;\r\n  \r\n  function utf8_encode (argString) {\r\n    // http://kevin.vanzonneveld.net\r\n    // +   original by: Webtoolkit.info (http://www.webtoolkit.info/)\r\n    // +   improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)\r\n    // +   improved by: sowberry\r\n    // +    tweaked by: Jack\r\n    // +   bugfixed by: Onno Marsman\r\n    // +   improved by: Yves Sucaet\r\n    // +   bugfixed by: Onno Marsman\r\n    // +   bugfixed by: Ulrich\r\n    // +   bugfixed by: Rafal Kukawski\r\n    // +   improved by: kirilloid\r\n    // *     example 1: utf8_encode('Kevin van Zonneveld');\r\n    // *     returns 1: 'Kevin van Zonneveld'\r\n\r\n    if (argString === null || typeof argString === \"undefined\") {\r\n      return \"\";\r\n    }\r\n\r\n    var string = (argString + ''); // .replace(/\\r\\n/g, \"\\n\").replace(/\\r/g, \"\\n\");\r\n    var utftext = '',\r\n        start, end, stringl = 0;\r\n\r\n    start = end = 0;\r\n    stringl = string.length;\r\n    for (var n = 0; n < stringl; n++) {\r\n      var c1 = string.charCodeAt(n);\r\n      var enc = null;\r\n\r\n      if (c1 < 128) {\r\n        end++;\r\n      } else if (c1 > 127 && c1 < 2048) {\r\n        enc = String.fromCharCode((c1 >> 6) | 192, (c1 & 63) | 128);\r\n      } else {\r\n        enc = String.fromCharCode((c1 >> 12) | 224, ((c1 >> 6) & 63) | 128, (c1 & 63) | 128);\r\n      }\r\n      if (enc !== null) {\r\n        if (end > start) {\r\n          utftext += string.slice(start, end);\r\n        }\r\n        utftext += enc;\r\n        start = end = n + 1;\r\n      }\r\n    }\r\n\r\n    if (end > start) {\r\n      utftext += string.slice(start, stringl);\r\n    }\r\n\r\n    return utftext;\r\n  }\r\n  \r\n  function crc32 (str) {\r\n    // http://kevin.vanzonneveld.net\r\n    // +   original by: Webtoolkit.info (http://www.webtoolkit.info/)\r\n    // +   improved by: T0bsn\r\n    // +   improved by: http://stackoverflow.com/questions/2647935/javascript-crc32-function-and-php-crc32-not-matching\r\n    // -    depends on: utf8_encode\r\n    // *     example 1: crc32('Kevin van Zonneveld');\r\n    // *     returns 1: 1249991249\r\n    str = utf8_encode(str);\r\n    var table = \"00000000 77073096 EE0E612C 990951BA 076DC419 706AF48F E963A535 9E6495A3 0EDB8832 79DCB8A4 E0D5E91E 97D2D988 09B64C2B 7EB17CBD E7B82D07 90BF1D91 1DB71064 6AB020F2 F3B97148 84BE41DE 1ADAD47D 6DDDE4EB F4D4B551 83D385C7 136C9856 646BA8C0 FD62F97A 8A65C9EC 14015C4F 63066CD9 FA0F3D63 8D080DF5 3B6E20C8 4C69105E D56041E4 A2677172 3C03E4D1 4B04D447 D20D85FD A50AB56B 35B5A8FA 42B2986C DBBBC9D6 ACBCF940 32D86CE3 45DF5C75 DCD60DCF ABD13D59 26D930AC 51DE003A C8D75180 BFD06116 21B4F4B5 56B3C423 CFBA9599 B8BDA50F 2802B89E 5F058808 C60CD9B2 B10BE924 2F6F7C87 58684C11 C1611DAB B6662D3D 76DC4190 01DB7106 98D220BC EFD5102A 71B18589 06B6B51F 9FBFE4A5 E8B8D433 7807C9A2 0F00F934 9609A88E E10E9818 7F6A0DBB 086D3D2D 91646C97 E6635C01 6B6B51F4 1C6C6162 856530D8 F262004E 6C0695ED 1B01A57B 8208F4C1 F50FC457 65B0D9C6 12B7E950 8BBEB8EA FCB9887C 62DD1DDF 15DA2D49 8CD37CF3 FBD44C65 4DB26158 3AB551CE A3BC0074 D4BB30E2 4ADFA541 3DD895D7 A4D1C46D D3D6F4FB 4369E96A 346ED9FC AD678846 DA60B8D0 44042D73 33031DE5 AA0A4C5F DD0D7CC9 5005713C 270241AA BE0B1010 C90C2086 5768B525 206F85B3 B966D409 CE61E49F 5EDEF90E 29D9C998 B0D09822 C7D7A8B4 59B33D17 2EB40D81 B7BD5C3B C0BA6CAD EDB88320 9ABFB3B6 03B6E20C 74B1D29A EAD54739 9DD277AF 04DB2615 73DC1683 E3630B12 94643B84 0D6D6A3E 7A6A5AA8 E40ECF0B 9309FF9D 0A00AE27 7D079EB1 F00F9344 8708A3D2 1E01F268 6906C2FE F762575D 806567CB 196C3671 6E6B06E7 FED41B76 89D32BE0 10DA7A5A 67DD4ACC F9B9DF6F 8EBEEFF9 17B7BE43 60B08ED5 D6D6A3E8 A1D1937E 38D8C2C4 4FDFF252 D1BB67F1 A6BC5767 3FB506DD 48B2364B D80D2BDA AF0A1B4C 36034AF6 41047A60 DF60EFC3 A867DF55 316E8EEF 4669BE79 CB61B38C BC66831A 256FD2A0 5268E236 CC0C7795 BB0B4703 220216B9 5505262F C5BA3BBE B2BD0B28 2BB45A92 5CB36A04 C2D7FFA7 B5D0CF31 2CD99E8B 5BDEAE1D 9B64C2B0 EC63F226 756AA39C 026D930A 9C0906A9 EB0E363F 72076785 05005713 95BF4A82 E2B87A14 7BB12BAE 0CB61B38 92D28E9B E5D5BE0D 7CDCEFB7 0BDBDF21 86D3D2D4 F1D4E242 68DDB3F8 1FDA836E 81BE16CD F6B9265B 6FB077E1 18B74777 88085AE6 FF0F6A70 66063BCA 11010B5C 8F659EFF F862AE69 616BFFD3 166CCF45 A00AE278 D70DD2EE 4E048354 3903B3C2 A7672661 D06016F7 4969474D 3E6E77DB AED16A4A D9D65ADC 40DF0B66 37D83BF0 A9BCAE53 DEBB9EC5 47B2CF7F 30B5FFE9 BDBDF21C CABAC28A 53B39330 24B4A3A6 BAD03605 CDD70693 54DE5729 23D967BF B3667A2E C4614AB8 5D681B02 2A6F2B94 B40BBE37 C30C8EA1 5A05DF1B 2D02EF8D\";\r\n\r\n    var crc = 0;\r\n    var x = 0;\r\n    var y = 0;\r\n\r\n    crc = crc ^ (-1);\r\n    for (var i = 0, iTop = str.length; i < iTop; i++) {\r\n      y = (crc ^ str.charCodeAt(i)) & 0xFF;\r\n      x = \"0x\" + table.substr(y * 9, 8);\r\n      crc = (crc >>> 8) ^ x;\r\n    }\r\n\r\n    crc = crc ^ (-1);\r\n    //convert to unsigned 32-bit int if needed\r\n    if (crc < 0) {crc += 4294967296;}\r\n    return crc;\r\n  }\r\n\r\n  function option_consume(options, option_name, default_value) {\r\n    var result = options[option_name];\r\n    delete options[option_name];\r\n    return typeof(result) == 'undefined' ? default_value : result;\r\n  }\r\n\r\n  function build_array(arg) {\r\n    if (!arg) {\r\n      return [];\r\n    } else if ($.isArray(arg)) {\r\n      return arg;\r\n    } else { \r\n      return [arg];\r\n    }\r\n  }\r\n\r\n  function present(value) {\r\n    return typeof value != 'undefined' && (\"\" + value).length > 0;\r\n  }\r\n\r\n  function process_base_transformations(options) {\r\n    var transformations = build_array(options.transformation);\r\n    var all_named = true;\r\n    for (var i = 0; i < transformations.length; i++) {\r\n      all_named = all_named && typeof(transformations[i]) == 'string';\r\n    }\r\n    if (all_named) {\r\n      return [];\r\n    }\r\n    delete options.transformation;\r\n    var base_transformations = []; \r\n    for (var i = 0; i < transformations.length; i++) {\r\n      var transformation = transformations[i];\r\n      if (typeof(transformation) == 'string') {\r\n        base_transformations.push(\"t_\" + transformation);\r\n      } else {\r\n        base_transformations.push(generate_transformation_string($.extend({}, transformation)));\r\n      }\r\n    }\r\n    return base_transformations;\r\n  }\r\n\r\n  function process_size(options) {\r\n    var size = option_consume(options, 'size');\r\n    if (size) {\r\n      var split_size = size.split(\"x\");\r\n      options.width = split_size[0];\r\n      options.height = split_size[1];\r\n    }    \r\n  }\r\n\r\n  function process_html_dimensions(options) {\r\n    var width = options.width, height = options.height;\r\n    var has_layer = options.overlay || options.underlay;     \r\n    var crop = options.crop;\r\n    var use_as_html_dimensions = !has_layer && !options.angle && crop != \"fit\" && crop != \"limit\" && crop != \"lfill\";\r\n    if (use_as_html_dimensions) {\r\n      if (width && !options.html_width && width !== \"auto\" && parseFloat(width) >= 1) options.html_width = width;\r\n      if (height && !options.html_height && parseFloat(height) >= 1) options.html_height = height;\r\n    }\r\n    if (!crop && !has_layer) {\r\n      delete options.width;\r\n      delete options.height;\r\n    }    \r\n  }\r\n\r\n  var TRANSFORMATION_PARAM_NAME_MAPPING = {\r\n    angle: 'a',\r\n    background: 'b',\r\n    border: 'bo',\r\n    color: 'co',\r\n    color_space: 'cs',\r\n    crop: 'c',\r\n    default_image: 'd',\r\n    delay: 'dl',\r\n    density: 'dn',\r\n    dpr: 'dpr',\r\n    effect: 'e',\r\n    fetch_format: 'f',\r\n    flags: 'fl',\r\n    gravity: 'g',\r\n    height: 'h',\r\n    opacity: 'o',\r\n    overlay: 'l',\r\n    page: 'pg',\r\n    prefix: 'p',\r\n    quality: 'q',\r\n    radius: 'r',\r\n    transformation: 't',\r\n    underlay: 'u',\r\n    width: 'w',\r\n    x: 'x',\r\n    y: 'y'\r\n  };\r\n\r\n  var TRANSFORMATION_PARAM_VALUE_MAPPING = {\r\n    angle: function(angle){ return build_array(angle).join(\".\"); },\r\n    background: function(background) { return background.replace(/^#/, 'rgb:');},\r\n    border: function(border) {\r\n      if ($.isPlainObject(border)) { \r\n        var border_width = \"\" + (border.width || 2);\r\n        var border_color = (border.color || \"black\").replace(/^#/, 'rgb:');\r\n        border = border_width + \"px_solid_\" + border_color;\r\n      }\r\n      return border;        \r\n    },\r\n    color: function(color) { return color.replace(/^#/, 'rgb:');},\r\n    dpr: function(dpr) {\r\n      dpr = dpr.toString();\r\n      if (dpr === \"auto\") {\r\n        return \"1.0\";\r\n      } else if (dpr.match(/^\\d+$/)) {\r\n        return dpr + \".0\";\r\n      } else {\r\n        return dpr;\r\n      }\r\n    },\r\n    effect: function(effect) { return build_array(effect).join(\":\");},\r\n    flags: function(flags) { return build_array(flags).join(\".\")},\r\n    transformation: function(transformation) { return build_array(transformation).join(\".\")}\r\n  };\r\n\r\n  function generate_transformation_string(options) {\r\n    var base_transformations = process_base_transformations(options);\r\n    process_size(options);\r\n    process_html_dimensions(options);\r\n    \r\n    var params = [];\r\n    for (var param in TRANSFORMATION_PARAM_NAME_MAPPING) {\r\n      var value = option_consume(options, param);\r\n      if (!present(value)) continue;\r\n      if (TRANSFORMATION_PARAM_VALUE_MAPPING[param]) {        \r\n        value = TRANSFORMATION_PARAM_VALUE_MAPPING[param](value);\r\n      }\r\n      if (!present(value)) continue;\r\n      params.push(TRANSFORMATION_PARAM_NAME_MAPPING[param] + \"_\" + value);\r\n    }\r\n    params.sort();\r\n\r\n    var raw_transformation = option_consume(options, 'raw_transformation');\r\n    if (present(raw_transformation)) params.push(raw_transformation);\r\n    var transformation = params.join(\",\");\r\n    if (present(transformation)) base_transformations.push(transformation);\r\n    return base_transformations.join(\"/\");\r\n  }\r\n\r\n  function absolutize(url) {\r\n    if (!url.match(/^https?:\\//)) {\r\n      var prefix = document.location.protocol + \"//\" + document.location.host;\r\n      if (url[0] == '?') {\r\n        prefix += document.location.pathname;\r\n      } else if (url[0] != '/') {\r\n        prefix += document.location.pathname.replace(/\\/[^\\/]*$/, '/');\r\n      }        \r\n      url = prefix + url;\r\n    }\r\n    return url;\r\n  }\r\n\r\n  function cloudinary_url(public_id, options) { \r\n    options = options || {};\r\n    var type = option_consume(options, 'type', 'upload');\r\n    if (type == 'fetch') {\r\n      options.fetch_format = options.fetch_format || option_consume(options, 'format');\r\n    }\r\n    var transformation = generate_transformation_string(options);\r\n    var resource_type = option_consume(options, 'resource_type', \"image\");\r\n    var version = option_consume(options, 'version');\r\n    var format = option_consume(options, 'format');\r\n    var cloud_name = option_consume(options, 'cloud_name', $.cloudinary.config().cloud_name);\r\n    if (!cloud_name) throw \"Unknown cloud_name\";\r\n    var private_cdn = option_consume(options, 'private_cdn', $.cloudinary.config().private_cdn);    \r\n    var secure_distribution = option_consume(options, 'secure_distribution', $.cloudinary.config().secure_distribution);    \r\n    var cname = option_consume(options, 'cname', $.cloudinary.config().cname);\r\n    var cdn_subdomain = option_consume(options, 'cdn_subdomain', $.cloudinary.config().cdn_subdomain);\r\n    var shorten = option_consume(options, 'shorten', $.cloudinary.config().shorten);\r\n    var secure = option_consume(options, 'secure', window.location.protocol == 'https:');\r\n    var protocol = option_consume(options, 'protocol', $.cloudinary.config().protocol);\r\n    var trust_public_id = option_consume(options, 'trust_public_id'); \r\n\r\n    if (type == 'fetch') {\r\n      public_id = absolutize(public_id); \r\n    }\r\n    \r\n    if (public_id.match(/^https?:/)) {\r\n      if (type == \"upload\" || type == \"asset\") return public_id;\r\n      public_id = encodeURIComponent(public_id).replace(/%3A/g, \":\").replace(/%2F/g, \"/\"); \r\n    } else {\r\n      // Make sure public_id is URI encoded.\r\n      public_id = encodeURIComponent(decodeURIComponent(public_id)).replace(/%3A/g, \":\").replace(/%2F/g, \"/\");      \r\n      if (format) {\r\n        if (!trust_public_id) public_id = public_id.replace(/\\.(jpg|png|gif|webp)$/, '');\r\n        public_id = public_id + \".\" + format;\r\n      }\r\n    }\r\n\r\n    var prefix = secure ? 'https://' : (window.location.protocol === 'file:' ? \"file://\" : 'http://');\r\n    prefix = protocol ? protocol + '//' : prefix;\r\n    if (cloud_name.match(/^\\//) && !secure) {\r\n      prefix = \"/res\" + cloud_name;\r\n    } else {\r\n      var shared_domain = !private_cdn;\r\n      if (secure) {        \r\n        if (!secure_distribution || secure_distribution == OLD_AKAMAI_SHARED_CDN) {\r\n          secure_distribution = private_cdn ? cloud_name + \"-res.cloudinary.com\" : SHARED_CDN;\r\n        }\r\n        shared_domain = shared_domain || secure_distribution == SHARED_CDN;\r\n        prefix += secure_distribution;\r\n      } else {\r\n        var subdomain = cdn_subdomain ? \"a\" + ((crc32(public_id) % 5) + 1) + \".\" : \"\";\r\n        var host = cname || (private_cdn ? cloud_name + \"-res.cloudinary.com\" : \"res.cloudinary.com\" );\r\n        prefix += subdomain + host;\r\n      }\r\n      if (shared_domain) prefix += \"/\" + cloud_name;\r\n    }\r\n    if (shorten && resource_type == \"image\" && type == \"upload\") {\r\n      resource_type = \"iu\";\r\n      type = undefined;\r\n    }\r\n    if (public_id.search(\"/\") >= 0 && !public_id.match(/^v[0-9]+/) && !public_id.match(/^https?:\\//) && !present(version)) {\r\n      version = 1;\r\n    }\r\n\r\n    var url = [prefix, resource_type, type, transformation, version ? \"v\" + version : \"\",\r\n               public_id].join(\"/\").replace(/([^:])\\/+/g, '$1/');\r\n    return url;\r\n  }\r\n\r\n  function default_stoppoints(width) {\r\n    return 10 * Math.ceil(width / 10);\r\n  }\r\n\r\n  function prepare_html_url(public_id, options) {\r\n    if ($.cloudinary.config('dpr') && !options.dpr) {\r\n      options.dpr = $.cloudinary.config('dpr');\r\n    }\r\n    var url = cloudinary_url(public_id, options);\r\n    var width = option_consume(options, 'html_width');\r\n    var height = option_consume(options, 'html_height');\r\n    if (width) options.width = width;\r\n    if (height) options.height = height;    \r\n    return url;\r\n  }\r\n\r\n  function get_config(name, options, default_value) {\r\n    var value = options[name] || $.cloudinary.config(name);\r\n    if (typeof(value) == 'undefined') value = default_value;\r\n    return value;\r\n  }\r\n\r\n  var cloudinary_config = null;\r\n  var responsive_config = null;\r\n  var responsive_resize_initialized = false;\r\n  var device_pixel_ratio_cache = {};\r\n\r\n  $.cloudinary = {\r\n    CF_SHARED_CDN: CF_SHARED_CDN,  \r\n    OLD_AKAMAI_SHARED_CDN: OLD_AKAMAI_SHARED_CDN,\r\n    AKAMAI_SHARED_CDN: AKAMAI_SHARED_CDN,\r\n    SHARED_CDN: SHARED_CDN,    \r\n    config: function(new_config, new_value) {\r\n      if (!cloudinary_config) {\r\n        cloudinary_config = {};\r\n        $('meta[name^=\"cloudinary_\"]').each(function() {\r\n          cloudinary_config[$(this).attr('name').replace(\"cloudinary_\", '')] = $(this).attr('content');\r\n        });\r\n      }\r\n      if (typeof(new_value) != 'undefined') {\r\n        cloudinary_config[new_config] = new_value;\r\n      } else if (typeof(new_config) == 'string') {\r\n        return cloudinary_config[new_config];\r\n      } else if (new_config) {\r\n        cloudinary_config = new_config;\r\n      }\r\n      return cloudinary_config;\r\n    },\r\n    url: function(public_id, options) {\r\n      options = $.extend({}, options);\r\n      return cloudinary_url(public_id, options);    \r\n    },    \r\n    url_internal: cloudinary_url,\r\n    transformation_string: function(options) {\r\n      options = $.extend({}, options);\r\n      return generate_transformation_string(options);\r\n    },\r\n    image: function(public_id, options) {\r\n      options = $.extend({}, options);\r\n      var url = prepare_html_url(public_id, options);\r\n      var img = $('<img/>').data('src-cache', url).attr(options).cloudinary_update(options);\r\n      return img;\r\n    },\r\n    facebook_profile_image: function(public_id, options) {\r\n      return $.cloudinary.image(public_id, $.extend({type: 'facebook'}, options));\r\n    },\r\n    twitter_profile_image: function(public_id, options) {\r\n      return $.cloudinary.image(public_id, $.extend({type: 'twitter'}, options));\r\n    },\r\n    twitter_name_profile_image: function(public_id, options) {\r\n      return $.cloudinary.image(public_id, $.extend({type: 'twitter_name'}, options));\r\n    },\r\n    gravatar_image: function(public_id, options) {\r\n      return $.cloudinary.image(public_id, $.extend({type: 'gravatar'}, options));\r\n    },\r\n    fetch_image: function(public_id, options) {\r\n      return $.cloudinary.image(public_id, $.extend({type: 'fetch'}, options));\r\n    },\r\n    sprite_css: function(public_id, options) {\r\n      options = $.extend({type: 'sprite'}, options);\r\n      if (!public_id.match(/.css$/)) options.format = 'css';\r\n      return $.cloudinary.url(public_id, options);\r\n    },\r\n    /** \r\n     * Turn on hidpi (dpr_auto) and responsive (w_auto) processing according to the current container size and the device pixel ratio.\r\n     * Use the following classes:\r\n     * - cld-hidpi - only set dpr_auto\r\n     * - cld-responsive - update both dpr_auto and w_auto\r\n     * @param: options\r\n     * - responsive_resize - should responsive images be updated on resize (default: true).\r\n     * - responsive_debounce - if set, how many milliseconds after resize is done before the image is replaces (default: 100). Set to 0 to disable.\r\n     * - responsive_use_stoppoints: \r\n     *   - true - always use stoppoints for width\r\n     *   - \"resize\" - use exact width on first render and stoppoints on resize (default)\r\n     *   - false - always use exact width\r\n     * Stoppoints - to prevent creating a transformation for every pixel, stop-points can be configured. The smallest stop-point that is larger than\r\n     *    the wanted width will be used. The default stoppoints are all the multiples of 10. See calc_stoppoint for ways to override this.\r\n     */\r\n    responsive: function(options) {\r\n      responsive_config = $.extend(responsive_config || {}, options);\r\n      $('img.cld-responsive, img.cld-hidpi').cloudinary_update(responsive_config);\r\n      var responsive_resize = get_config('responsive_resize', responsive_config, true);\r\n      if (responsive_resize && !responsive_resize_initialized) {\r\n        responsive_config.resizing = responsive_resize_initialized = true;\r\n        var timeout = null;\r\n        $(window).on('resize', function() {\r\n          var debounce = get_config('responsive_debounce', responsive_config, 100);\r\n          function reset() {\r\n            if (timeout) {\r\n              clearTimeout(timeout); \r\n              timeout = null;\r\n            }\r\n          }\r\n          function run() {\r\n            $('img.cld-responsive').cloudinary_update(responsive_config);  \r\n          }\r\n          function wait() {\r\n            reset();\r\n            setTimeout(function() { reset(); run(); }, debounce);\r\n          }\r\n          if (debounce) {\r\n            wait();\r\n          } else {\r\n            run();\r\n          }\r\n        });\r\n      }\r\n    },    \r\n    /**\r\n     * Compute the stoppoint for the given element and width.\r\n     * By default the stoppoint will be the smallest multiple of 10 larger than the width.\r\n     * These can be overridden by either setting the data-stoppoints attribute of an image or using $.cloudinary.config('stoppoints', stoppoints).\r\n     * The value can be either:\r\n     * - an ordered list of the wanted stoppoints\r\n     * - a comma separated ordered list of stoppoints\r\n     * - a function that returns the stoppoint given the wanted width.\r\n     */\r\n    calc_stoppoint: function (element, width) {\r\n      var stoppoints = $(element).data('stoppoints') || $.cloudinary.config().stoppoints || default_stoppoints;\r\n      if (typeof(stoppoints) === 'function') {\r\n        return stoppoints(width);\r\n      }\r\n      if (typeof(stoppoints) === 'string') {\r\n        stoppoints = $.map(stoppoints.split(\",\"), function(val){ return parseInt(val); });\r\n      }\r\n      var i = stoppoints.length - 2;\r\n      while (i >= 0 && stoppoints[i] >= width) {\r\n        i--;\r\n      }\r\n      return stoppoints[i+1];\r\n    },\r\n    device_pixel_ratio: function() {    \r\n      var dpr = window.devicePixelRatio || 1;\r\n      var dpr_string = device_pixel_ratio_cache[dpr];      \r\n      if (!dpr_string) {\r\n        dpr_string = dpr.toString();\r\n        if (dpr_string.match(/^\\d+$/)) dpr_string += \".0\";\r\n        device_pixel_ratio_cache[dpr] = dpr_string;\r\n      }\r\n      return dpr_string;\r\n    }\r\n  };\r\n\r\n  $.fn.cloudinary = function(options) {\r\n    this.filter('img').each(function() {\r\n      var img_options = $.extend({width: $(this).attr('width'), height: $(this).attr('height'),\r\n                                  src: $(this).attr('src')}, $(this).data(), options);\r\n      var public_id = option_consume(img_options, 'source', option_consume(img_options, 'src')); \r\n      var url = prepare_html_url(public_id, img_options);\r\n      $(this).data('src-cache', url).attr({width: img_options.width, height: img_options.height});\r\n    }).cloudinary_update(options);\r\n    return this;\r\n  };\r\n\r\n  /** \r\n   * Update hidpi (dpr_auto) and responsive (w_auto) fields according to the current container size and the device pixel ratio.\r\n   * Only images marked with the cld-responsive class have w_auto updated.\r\n   * options: \r\n   * - responsive_use_stoppoints: \r\n   *   - true - always use stoppoints for width\r\n   *   - \"resize\" - use exact width on first render and stoppoints on resize (default)\r\n   *   - false - always use exact width\r\n   * - responsive:\r\n   *   - true - enable responsive on this element. Can be done by adding cld-responsive.\r\n   *            Note that $.cloudinary.responsive() should be called once on the page. \r\n   */\r\n  $.fn.cloudinary_update = function(options) {\r\n    options = options || {};\r\n    var responsive_use_stoppoints = get_config('responsive_use_stoppoints', options, \"resize\");\r\n    var exact = responsive_use_stoppoints === false || (responsive_use_stoppoints == \"resize\" && !options.resizing);\r\n\r\n    this.filter('img').each(function() {\r\n      if (options.responsive) {\r\n        $(this).addClass('cld-responsive');\r\n      }\r\n      var attrs = {};\r\n      var src = $(this).data('src-cache') || $(this).data('src');\r\n\r\n      if (!src) return;\r\n      var responsive = $(this).hasClass('cld-responsive') && src.match(/\\bw_auto\\b/);\r\n      if (responsive) {\r\n        var container = $(this).parent()[0];\r\n        var containerWidth = container ? container.clientWidth : 0;\r\n        if (containerWidth == 0) {\r\n          // container doesn't know the size yet. Usually because the image is hidden or outside the DOM.\r\n          return; \r\n        }\r\n        \r\n        var requestedWidth = exact ? containerWidth : $.cloudinary.calc_stoppoint(this, containerWidth);          \r\n        var currentWidth = $(this).data('width') || 0; \r\n        if (requestedWidth > currentWidth) {\r\n          // requested width is larger, fetch new image\r\n          $(this).data('width', requestedWidth);\r\n        } else {\r\n          // requested width is not larger - keep previous\r\n          requestedWidth = currentWidth;\r\n        }\r\n        src = src.replace(/\\bw_auto\\b/g, \"w_\" + requestedWidth);\r\n        attrs.width = null;\r\n        attrs.height = null;\r\n      }\r\n      // Update dpr according to the device's devicePixelRatio\r\n      attrs.src = src.replace(/\\bdpr_(1\\.0|auto)\\b/g, \"dpr_\" + $.cloudinary.device_pixel_ratio());\r\n      $(this).attr(attrs);\r\n    });\r\n    return this;\r\n  };\r\n\r\n\r\n  var webp = null;\r\n  $.fn.webpify = function(options, webp_options) {\r\n    var that = this;\r\n    options = options || {};\r\n    webp_options = webp_options || options;\r\n    if (!webp) { \r\n      webp = $.Deferred();\r\n      var webp_canary = new Image();\r\n      webp_canary.onerror = webp.reject;\r\n      webp_canary.onload = webp.resolve;\r\n      webp_canary.src = 'data:image/webp;base64,UklGRi4AAABXRUJQVlA4TCEAAAAvAUAAEB8wAiMwAgSSNtse/cXjxyCCmrYNWPwmHRH9jwMA';\r\n    }\r\n    $(function() {\r\n      webp.done(function() {\r\n        $(that).cloudinary($.extend({}, webp_options, {format: 'webp'}));\r\n      }).fail(function() {\r\n        $(that).cloudinary(options);\r\n      });\r\n    });\r\n    return this;\r\n  };\r\n  $.fn.fetchify = function(options) {\r\n    return this.cloudinary($.extend(options, {'type': 'fetch'}));\r\n  };\r\n  if (!$.fn.fileupload) {\r\n    return;\r\n  }\r\n  $.cloudinary.delete_by_token = function(delete_token, options) {\r\n    options = options || {};\r\n    var url = options.url;\r\n    if (!url) {\r\n      var cloud_name = options.cloud_name || $.cloudinary.config().cloud_name;\r\n      url = \"https://api.cloudinary.com/v1_1/\" + cloud_name + \"/delete_by_token\";\r\n    }\r\n    var dataType = $.support.xhrFileUpload ? \"json\" : \"iframe json\";\r\n    return $.ajax({\r\n      url: url,\r\n      method: \"POST\",\r\n      data: {token: delete_token},\r\n      headers: {\"X-Requested-With\": \"XMLHttpRequest\"},\r\n      dataType: dataType\r\n    });\r\n  };\r\n\r\n  $.fn.cloudinary_fileupload = function(options) {\r\n    var initializing = !this.data('blueimpFileupload');\r\n    if (initializing) {\r\n      options = $.extend({\r\n        maxFileSize: 20000000,\r\n        dataType: 'json',\r\n        headers: {\"X-Requested-With\": \"XMLHttpRequest\"}\r\n      }, options);\r\n    }\r\n    this.fileupload(options);\r\n    \r\n    if (initializing) {\r\n      this.bind(\"fileuploaddone\", function(e, data) {\r\n        if (data.result.error) return;      \r\n        data.result.path = [\"v\", data.result.version, \"/\", data.result.public_id, \r\n                            data.result.format ? \".\" + data.result.format : \"\"].join(\"\");\r\n    \r\n        if (data.cloudinaryField && data.form.length > 0) {\r\n          var upload_info = [data.result.resource_type, data.result.type, data.result.path].join(\"/\") + \"#\" + data.result.signature;  \r\n          var multiple = $(e.target).prop(\"multiple\");\r\n          var add_field = function() {\r\n            $('<input></input>').attr({type: \"hidden\", name: data.cloudinaryField}).val(upload_info).appendTo(data.form);\r\n          };\r\n          \r\n          if (multiple) {\r\n            add_field();\r\n          } else {          \r\n            var field = $(data.form).find('input[name=\"' + data.cloudinaryField + '\"]');\r\n            if (field.length > 0) {\r\n              field.val(upload_info);\r\n            } else {\r\n              add_field();\r\n            }\r\n          }\r\n        }\r\n        $(e.target).trigger('cloudinarydone', data);\r\n      });\r\n      \r\n      this.bind(\"fileuploadstart\", function(e){\r\n        $(e.target).trigger('cloudinarystart');\r\n      });\r\n      this.bind(\"fileuploadstop\", function(e){\r\n        $(e.target).trigger('cloudinarystop');\r\n      });\r\n      this.bind(\"fileuploadprogress\", function(e,data){\r\n        $(e.target).trigger('cloudinaryprogress',data);\r\n      });\r\n      this.bind(\"fileuploadprogressall\", function(e,data){\r\n        $(e.target).trigger('cloudinaryprogressall',data);\r\n      });\r\n      this.bind(\"fileuploadfail\", function(e,data){\r\n        $(e.target).trigger('cloudinaryfail',data);\r\n      });\r\n      this.bind(\"fileuploadalways\", function(e,data){\r\n        $(e.target).trigger('cloudinaryalways',data);\r\n      });\r\n\r\n      if (!this.fileupload('option').url) {\r\n        var cloud_name = options.cloud_name || $.cloudinary.config().cloud_name;\r\n        var upload_url = \"https://api.cloudinary.com/v1_1/\" + cloud_name + \"/upload\";\r\n        this.fileupload('option', 'url', upload_url);\r\n      }\r\n    }\r\n    return this;\r\n  };\r\n  \r\n  $.fn.cloudinary_upload_url = function(remote_url) {\r\n    this.fileupload('option', 'formData').file = remote_url; \r\n    this.fileupload('add', { files: [ remote_url ] }); \r\n    delete(this.fileupload('option', 'formData').file);    \r\n  };\r\n    \r\n  $.fn.unsigned_cloudinary_upload = function(upload_preset, upload_params, options) {\r\n    options = options || {};\r\n    upload_params = $.extend({}, upload_params) || {};\r\n\r\n    if (upload_params.cloud_name) {\r\n      options.cloud_name = upload_params.cloud_name;\r\n      delete upload_params.cloud_name;\r\n    }\r\n\r\n    // Serialize upload_params\r\n    for (var key in upload_params) {\r\n      var value = upload_params[key];\r\n      if ($.isPlainObject(value)) {\r\n        upload_params[key] = $.map(value, function(v, k){return k + \"=\" + v;}).join(\"|\");      \r\n      } else if ($.isArray(value)) {\r\n        if (value.length > 0 && $.isArray(value[0])) {\r\n          upload_params[key] = $.map(value, function(array_value){return array_value.join(\",\");}).join(\"|\");                \r\n        } else {\r\n          upload_params[key] = value.join(\",\");  \r\n        }\r\n      }\r\n    }\r\n    if (!upload_params.callback) {\r\n      upload_params.callback = \"/cloudinary_cors.html\";\r\n    }\r\n    upload_params.upload_preset = upload_preset;\r\n\r\n    options.formData = upload_params;\r\n\r\n    if (options.cloudinary_field) {\r\n      options.cloudinaryField = options.cloudinary_field;\r\n      delete options.cloudinary_field;\r\n    }\r\n        \r\n    var html_options = options.html || {};\r\n    html_options[\"class\"] = \"cloudinary_fileupload \" + (html_options[\"class\"] || \"\");\r\n    if (options.multiple) html_options.multiple = true;\r\n    this.attr(html_options).cloudinary_fileupload(options);\r\n    return this;\r\n  };\r\n  \r\n  $.cloudinary.unsigned_upload_tag = function(upload_preset, upload_params, options) {\r\n    return $('<input/>').attr({type: \"file\", name: \"file\"}).unsigned_cloudinary_upload(upload_preset, upload_params, options);\r\n  };\r\n}));"
  },
  {
    "path": "samples/photo_album/src/main/webapp/assets/javascripts/cloudinary/jquery.fileupload-image.js",
    "content": "/*\n * jQuery File Upload Image Preview & Resize Plugin 1.2.2\n * https://github.com/blueimp/jQuery-File-Upload\n *\n * Copyright 2013, Sebastian Tschan\n * https://blueimp.net\n *\n * Licensed under the MIT license:\n * http://www.opensource.org/licenses/MIT\n */\n\n/*jslint nomen: true, unparam: true, regexp: true */\n/*global define, window, document, DataView, Blob, Uint8Array */\n\n(function (factory) {\n    'use strict';\n    if (typeof define === 'function' && define.amd) {\n        // Register as an anonymous AMD module:\n        define([\n            'jquery',\n            'load-image',\n            'load-image-meta',\n            'load-image-exif',\n            'load-image-ios',\n            'canvas-to-blob',\n            './jquery.fileupload-process'\n        ], factory);\n    } else {\n        // Browser globals:\n        factory(\n            window.jQuery,\n            window.loadImage\n        );\n    }\n}(function ($, loadImage) {\n    'use strict';\n\n    // Prepend to the default processQueue:\n    $.blueimp.fileupload.prototype.options.processQueue.unshift(\n        {\n            action: 'loadImageMetaData',\n            // Always trigger this action,\n            // even if the previous action was rejected: \n            always: true,\n            disableImageHead: '@',\n            disableExif: '@',\n            disableExifThumbnail: '@',\n            disableExifSub: '@',\n            disableExifGps: '@',\n            disabled: '@disableImageMetaDataLoad'\n        },\n        {\n            action: 'loadImage',\n            // Use the action as prefix for the \"@\" options:\n            prefix: true,\n            fileTypes: '@',\n            maxFileSize: '@',\n            noRevoke: '@',\n            disabled: '@disableImageLoad'\n        },\n        {\n            action: 'resizeImage',\n            // Use \"image\" as prefix for the \"@\" options:\n            prefix: 'image',\n            maxWidth: '@',\n            maxHeight: '@',\n            minWidth: '@',\n            minHeight: '@',\n            crop: '@',\n            disabled: '@disableImageResize'\n        },\n        {\n            action: 'saveImage',\n            disabled: '@disableImageResize'\n        },\n        {\n            action: 'saveImageMetaData',\n            disabled: '@disableImageMetaDataSave'\n        },\n        {\n            action: 'resizeImage',\n            // Always trigger this action,\n            // even if the previous action was rejected: \n            always: true,\n            // Use \"preview\" as prefix for the \"@\" options:\n            prefix: 'preview',\n            maxWidth: '@',\n            maxHeight: '@',\n            minWidth: '@',\n            minHeight: '@',\n            crop: '@',\n            orientation: '@',\n            thumbnail: '@',\n            canvas: '@',\n            disabled: '@disableImagePreview'\n        },\n        {\n            action: 'setImage',\n            name: '@imagePreviewName',\n            disabled: '@disableImagePreview'\n        }\n    );\n\n    // The File Upload Resize plugin extends the fileupload widget\n    // with image resize functionality:\n    $.widget('blueimp.fileupload', $.blueimp.fileupload, {\n\n        options: {\n            // The regular expression for the types of images to load:\n            // matched against the file type:\n            loadImageFileTypes: /^image\\/(gif|jpeg|png)$/,\n            // The maximum file size of images to load:\n            loadImageMaxFileSize: 10000000, // 10MB\n            // The maximum width of resized images:\n            imageMaxWidth: 1920,\n            // The maximum height of resized images:\n            imageMaxHeight: 1080,\n            // Define if resized images should be cropped or only scaled:\n            imageCrop: false,\n            // Disable the resize image functionality by default:\n            disableImageResize: true,\n            // The maximum width of the preview images:\n            previewMaxWidth: 80,\n            // The maximum height of the preview images:\n            previewMaxHeight: 80,\n            // Defines the preview orientation (1-8) or takes the orientation\n            // value from Exif data if set to true:\n            previewOrientation: true,\n            // Create the preview using the Exif data thumbnail:\n            previewThumbnail: true,\n            // Define if preview images should be cropped or only scaled:\n            previewCrop: false,\n            // Define if preview images should be resized as canvas elements:\n            previewCanvas: true\n        },\n\n        processActions: {\n\n            // Loads the image given via data.files and data.index\n            // as img element if the browser supports canvas.\n            // Accepts the options fileTypes (regular expression)\n            // and maxFileSize (integer) to limit the files to load:\n            loadImage: function (data, options) {\n                if (options.disabled) {\n                    return data;\n                }\n                var that = this,\n                    file = data.files[data.index],\n                    dfd = $.Deferred();\n                if (($.type(options.maxFileSize) === 'number' &&\n                            file.size > options.maxFileSize) ||\n                        (options.fileTypes &&\n                            !options.fileTypes.test(file.type)) ||\n                        !loadImage(\n                            file,\n                            function (img) {\n                                if (!img.src) {\n                                    return dfd.rejectWith(that, [data]);\n                                }\n                                data.img = img;\n                                dfd.resolveWith(that, [data]);\n                            },\n                            options\n                        )) {\n                    dfd.rejectWith(that, [data]);\n                }\n                return dfd.promise();\n            },\n\n            // Resizes the image given as data.canvas or data.img\n            // and updates data.canvas or data.img with the resized image.\n            // Accepts the options maxWidth, maxHeight, minWidth,\n            // minHeight, canvas and crop:\n            resizeImage: function (data, options) {\n                if (options.disabled) {\n                    return data;\n                }\n                var that = this,\n                    dfd = $.Deferred(),\n                    resolve = function (newImg) {\n                        data[newImg.getContext ? 'canvas' : 'img'] = newImg;\n                        dfd.resolveWith(that, [data]);\n                    },\n                    thumbnail,\n                    img,\n                    newImg;\n                options = $.extend({canvas: true}, options);\n                if (data.exif) {\n                    if (options.orientation === true) {\n                        options.orientation = data.exif.get('Orientation');\n                    }\n                    if (options.thumbnail) {\n                        thumbnail = data.exif.get('Thumbnail');\n                        if (thumbnail) {\n                            loadImage(thumbnail, resolve, options);\n                            return dfd.promise();\n                        }\n                    }\n                }\n                img = (options.canvas && data.canvas) || data.img;\n                if (img) {\n                    newImg = loadImage.scale(img, options);\n                    if (newImg.width !== img.width ||\n                            newImg.height !== img.height) {\n                        resolve(newImg);\n                        return dfd.promise();\n                    }\n                }\n                return data;\n            },\n\n            // Saves the processed image given as data.canvas\n            // inplace at data.index of data.files:\n            saveImage: function (data, options) {\n                if (!data.canvas || options.disabled) {\n                    return data;\n                }\n                var that = this,\n                    file = data.files[data.index],\n                    name = file.name,\n                    dfd = $.Deferred(),\n                    callback = function (blob) {\n                        if (!blob.name) {\n                            if (file.type === blob.type) {\n                                blob.name = file.name;\n                            } else if (file.name) {\n                                blob.name = file.name.replace(\n                                    /\\..+$/,\n                                    '.' + blob.type.substr(6)\n                                );\n                            }\n                        }\n                        // Store the created blob at the position\n                        // of the original file in the files list:\n                        data.files[data.index] = blob;\n                        dfd.resolveWith(that, [data]);\n                    };\n                // Use canvas.mozGetAsFile directly, to retain the filename, as\n                // Gecko doesn't support the filename option for FormData.append:\n                if (data.canvas.mozGetAsFile) {\n                    callback(data.canvas.mozGetAsFile(\n                        (/^image\\/(jpeg|png)$/.test(file.type) && name) ||\n                            ((name && name.replace(/\\..+$/, '')) ||\n                                'blob') + '.png',\n                        file.type\n                    ));\n                } else if (data.canvas.toBlob) {\n                    data.canvas.toBlob(callback, file.type);\n                } else {\n                    return data;\n                }\n                return dfd.promise();\n            },\n\n            loadImageMetaData: function (data, options) {\n                if (options.disabled) {\n                    return data;\n                }\n                var that = this,\n                    dfd = $.Deferred();\n                loadImage.parseMetaData(data.files[data.index], function (result) {\n                    $.extend(data, result);\n                    dfd.resolveWith(that, [data]);\n                }, options);\n                return dfd.promise();\n            },\n\n            saveImageMetaData: function (data, options) {\n                if (!(data.imageHead && data.canvas &&\n                        data.canvas.toBlob && !options.disabled)) {\n                    return data;\n                }\n                var file = data.files[data.index],\n                    blob = new Blob([\n                        data.imageHead,\n                        // Resized images always have a head size of 20 bytes,\n                        // including the JPEG marker and a minimal JFIF header:\n                        this._blobSlice.call(file, 20)\n                    ], {type: file.type});\n                blob.name = file.name;\n                data.files[data.index] = blob;\n                return data;\n            },\n\n            // Sets the resized version of the image as a property of the\n            // file object, must be called after \"saveImage\":\n            setImage: function (data, options) {\n                var img = data.canvas || data.img;\n                if (img && !options.disabled) {\n                    data.files[data.index][options.name || 'preview'] = img;\n                }\n                return data;\n            }\n\n        }\n\n    });\n\n}));\n"
  },
  {
    "path": "samples/photo_album/src/main/webapp/assets/javascripts/cloudinary/jquery.fileupload-process.js",
    "content": "/*\n * jQuery File Upload Processing Plugin 1.2.2\n * https://github.com/blueimp/jQuery-File-Upload\n *\n * Copyright 2012, Sebastian Tschan\n * https://blueimp.net\n *\n * Licensed under the MIT license:\n * http://www.opensource.org/licenses/MIT\n */\n\n/*jslint nomen: true, unparam: true */\n/*global define, window */\n\n(function (factory) {\n    'use strict';\n    if (typeof define === 'function' && define.amd) {\n        // Register as an anonymous AMD module:\n        define([\n            'jquery',\n            './jquery.fileupload'\n        ], factory);\n    } else {\n        // Browser globals:\n        factory(\n            window.jQuery\n        );\n    }\n}(function ($) {\n    'use strict';\n\n    var originalAdd = $.blueimp.fileupload.prototype.options.add;\n\n    // The File Upload Processing plugin extends the fileupload widget\n    // with file processing functionality:\n    $.widget('blueimp.fileupload', $.blueimp.fileupload, {\n\n        options: {\n            // The list of processing actions:\n            processQueue: [\n                /*\n                {\n                    action: 'log',\n                    type: 'debug'\n                }\n                */\n            ],\n            add: function (e, data) {\n                var $this = $(this);\n                data.process(function () {\n                    return $this.fileupload('process', data);\n                });\n                originalAdd.call(this, e, data);\n            }\n        },\n\n        processActions: {\n            /*\n            log: function (data, options) {\n                console[options.type](\n                    'Processing \"' + data.files[data.index].name + '\"'\n                );\n            }\n            */\n        },\n\n        _processFile: function (data) {\n            var that = this,\n                dfd = $.Deferred().resolveWith(that, [data]),\n                chain = dfd.promise();\n            this._trigger('process', null, data);\n            $.each(data.processQueue, function (i, settings) {\n                var func = function (data) {\n                    return that.processActions[settings.action].call(\n                        that,\n                        data,\n                        settings\n                    );\n                };\n                chain = chain.pipe(func, settings.always && func);\n            });\n            chain\n                .done(function () {\n                    that._trigger('processdone', null, data);\n                    that._trigger('processalways', null, data);\n                })\n                .fail(function () {\n                    that._trigger('processfail', null, data);\n                    that._trigger('processalways', null, data);\n                });\n            return chain;\n        },\n\n        // Replaces the settings of each processQueue item that\n        // are strings starting with an \"@\", using the remaining\n        // substring as key for the option map,\n        // e.g. \"@autoUpload\" is replaced with options.autoUpload:\n        _transformProcessQueue: function (options) {\n            var processQueue = [];\n            $.each(options.processQueue, function () {\n                var settings = {},\n                    action = this.action,\n                    prefix = this.prefix === true ? action : this.prefix;\n                $.each(this, function (key, value) {\n                    if ($.type(value) === 'string' &&\n                            value.charAt(0) === '@') {\n                        settings[key] = options[\n                            value.slice(1) || (prefix ? prefix +\n                                key.charAt(0).toUpperCase() + key.slice(1) : key)\n                        ];\n                    } else {\n                        settings[key] = value;\n                    }\n\n                });\n                processQueue.push(settings);\n            });\n            options.processQueue = processQueue;\n        },\n\n        // Returns the number of files currently in the processsing queue:\n        processing: function () {\n            return this._processing;\n        },\n\n        // Processes the files given as files property of the data parameter,\n        // returns a Promise object that allows to bind callbacks:\n        process: function (data) {\n            var that = this,\n                options = $.extend({}, this.options, data);\n            if (options.processQueue && options.processQueue.length) {\n                this._transformProcessQueue(options);\n                if (this._processing === 0) {\n                    this._trigger('processstart');\n                }\n                $.each(data.files, function (index) {\n                    var opts = index ? $.extend({}, options) : options,\n                        func = function () {\n                            return that._processFile(opts);\n                        };\n                    opts.index = index;\n                    that._processing += 1;\n                    that._processingQueue = that._processingQueue.pipe(func, func)\n                        .always(function () {\n                            that._processing -= 1;\n                            if (that._processing === 0) {\n                                that._trigger('processstop');\n                            }\n                        });\n                });\n            }\n            return this._processingQueue;\n        },\n\n        _create: function () {\n            this._super();\n            this._processing = 0;\n            this._processingQueue = $.Deferred().resolveWith(this)\n                .promise();\n        }\n\n    });\n\n}));\n"
  },
  {
    "path": "samples/photo_album/src/main/webapp/assets/javascripts/cloudinary/jquery.fileupload-validate.js",
    "content": "/*\n * jQuery File Upload Validation Plugin 1.1\n * https://github.com/blueimp/jQuery-File-Upload\n *\n * Copyright 2013, Sebastian Tschan\n * https://blueimp.net\n *\n * Licensed under the MIT license:\n * http://www.opensource.org/licenses/MIT\n */\n\n/*jslint nomen: true, unparam: true, regexp: true */\n/*global define, window */\n\n(function (factory) {\n    'use strict';\n    if (typeof define === 'function' && define.amd) {\n        // Register as an anonymous AMD module:\n        define([\n            'jquery',\n            './jquery.fileupload-process'\n        ], factory);\n    } else {\n        // Browser globals:\n        factory(\n            window.jQuery\n        );\n    }\n}(function ($) {\n    'use strict';\n\n    // Append to the default processQueue:\n    $.blueimp.fileupload.prototype.options.processQueue.push(\n        {\n            action: 'validate',\n            // Always trigger this action,\n            // even if the previous action was rejected: \n            always: true,\n            // Options taken from the global options map:\n            acceptFileTypes: '@',\n            maxFileSize: '@',\n            minFileSize: '@',\n            maxNumberOfFiles: '@',\n            disabled: '@disableValidation'\n        }\n    );\n\n    // The File Upload Validation plugin extends the fileupload widget\n    // with file validation functionality:\n    $.widget('blueimp.fileupload', $.blueimp.fileupload, {\n\n        options: {\n            /*\n            // The regular expression for allowed file types, matches\n            // against either file type or file name:\n            acceptFileTypes: /(\\.|\\/)(gif|jpe?g|png)$/i,\n            // The maximum allowed file size in bytes:\n            maxFileSize: 10000000, // 10 MB\n            // The minimum allowed file size in bytes:\n            minFileSize: undefined, // No minimal file size\n            // The limit of files to be uploaded:\n            maxNumberOfFiles: 10,\n            */\n\n            // Function returning the current number of files,\n            // has to be overriden for maxNumberOfFiles validation:\n            getNumberOfFiles: $.noop,\n\n            // Error and info messages:\n            messages: {\n                maxNumberOfFiles: 'Maximum number of files exceeded',\n                acceptFileTypes: 'File type not allowed',\n                maxFileSize: 'File is too large',\n                minFileSize: 'File is too small'\n            }\n        },\n\n        processActions: {\n\n            validate: function (data, options) {\n                if (options.disabled) {\n                    return data;\n                }\n                var dfd = $.Deferred(),\n                    settings = this.options,\n                    file = data.files[data.index],\n                    numberOfFiles = settings.getNumberOfFiles();\n                if (numberOfFiles && $.type(options.maxNumberOfFiles) === 'number' &&\n                        numberOfFiles + data.files.length > options.maxNumberOfFiles) {\n                    file.error = settings.i18n('maxNumberOfFiles');\n                } else if (options.acceptFileTypes &&\n                        !(options.acceptFileTypes.test(file.type) ||\n                        options.acceptFileTypes.test(file.name))) {\n                    file.error = settings.i18n('acceptFileTypes');\n                } else if (options.maxFileSize && file.size > options.maxFileSize) {\n                    file.error = settings.i18n('maxFileSize');\n                } else if ($.type(file.size) === 'number' &&\n                        file.size < options.minFileSize) {\n                    file.error = settings.i18n('minFileSize');\n                } else {\n                    delete file.error;\n                }\n                if (file.error || data.files.error) {\n                    data.files.error = true;\n                    dfd.rejectWith(this, [data]);\n                } else {\n                    dfd.resolveWith(this, [data]);\n                }\n                return dfd.promise();\n            }\n\n        }\n\n    });\n\n}));\n"
  },
  {
    "path": "samples/photo_album/src/main/webapp/assets/javascripts/cloudinary/jquery.fileupload.js",
    "content": "/*\n * jQuery File Upload Plugin 5.31.6\n * https://github.com/blueimp/jQuery-File-Upload\n *\n * Copyright 2010, Sebastian Tschan\n * https://blueimp.net\n *\n * Licensed under the MIT license:\n * http://www.opensource.org/licenses/MIT\n */\n\n/*jslint nomen: true, unparam: true, regexp: true */\n/*global define, window, document, location, File, Blob, FormData */\n\n(function (factory) {\n    'use strict';\n    if (typeof define === 'function' && define.amd) {\n        // Register as an anonymous AMD module:\n        define([\n            'jquery',\n            'jquery.ui.widget'\n        ], factory);\n    } else {\n        // Browser globals:\n        factory(window.jQuery);\n    }\n}(function ($) {\n    'use strict';\n\n    // The FileReader API is not actually used, but works as feature detection,\n    // as e.g. Safari supports XHR file uploads via the FormData API,\n    // but not non-multipart XHR file uploads:\n    $.support.xhrFileUpload = !!(window.XMLHttpRequestUpload && window.FileReader);\n    $.support.xhrFormDataFileUpload = !!window.FormData;\n\n    // Detect support for Blob slicing (required for chunked uploads):\n    $.support.blobSlice = window.Blob && (Blob.prototype.slice ||\n        Blob.prototype.webkitSlice || Blob.prototype.mozSlice);\n\n    // The fileupload widget listens for change events on file input fields defined\n    // via fileInput setting and paste or drop events of the given dropZone.\n    // In addition to the default jQuery Widget methods, the fileupload widget\n    // exposes the \"add\" and \"send\" methods, to add or directly send files using\n    // the fileupload API.\n    // By default, files added via file input selection, paste, drag & drop or\n    // \"add\" method are uploaded immediately, but it is possible to override\n    // the \"add\" callback option to queue file uploads.\n    $.widget('blueimp.fileupload', {\n\n        options: {\n            // The drop target element(s), by the default the complete document.\n            // Set to null to disable drag & drop support:\n            dropZone: $(document),\n            // The paste target element(s), by the default the complete document.\n            // Set to null to disable paste support:\n            pasteZone: $(document),\n            // The file input field(s), that are listened to for change events.\n            // If undefined, it is set to the file input fields inside\n            // of the widget element on plugin initialization.\n            // Set to null to disable the change listener.\n            fileInput: undefined,\n            // By default, the file input field is replaced with a clone after\n            // each input field change event. This is required for iframe transport\n            // queues and allows change events to be fired for the same file\n            // selection, but can be disabled by setting the following option to false:\n            replaceFileInput: true,\n            // The parameter name for the file form data (the request argument name).\n            // If undefined or empty, the name property of the file input field is\n            // used, or \"files[]\" if the file input name property is also empty,\n            // can be a string or an array of strings:\n            paramName: undefined,\n            // By default, each file of a selection is uploaded using an individual\n            // request for XHR type uploads. Set to false to upload file\n            // selections in one request each:\n            singleFileUploads: true,\n            // To limit the number of files uploaded with one XHR request,\n            // set the following option to an integer greater than 0:\n            limitMultiFileUploads: undefined,\n            // Set the following option to true to issue all file upload requests\n            // in a sequential order:\n            sequentialUploads: false,\n            // To limit the number of concurrent uploads,\n            // set the following option to an integer greater than 0:\n            limitConcurrentUploads: undefined,\n            // Set the following option to true to force iframe transport uploads:\n            forceIframeTransport: false,\n            // Set the following option to the location of a redirect url on the\n            // origin server, for cross-domain iframe transport uploads:\n            redirect: undefined,\n            // The parameter name for the redirect url, sent as part of the form\n            // data and set to 'redirect' if this option is empty:\n            redirectParamName: undefined,\n            // Set the following option to the location of a postMessage window,\n            // to enable postMessage transport uploads:\n            postMessage: undefined,\n            // By default, XHR file uploads are sent as multipart/form-data.\n            // The iframe transport is always using multipart/form-data.\n            // Set to false to enable non-multipart XHR uploads:\n            multipart: true,\n            // To upload large files in smaller chunks, set the following option\n            // to a preferred maximum chunk size. If set to 0, null or undefined,\n            // or the browser does not support the required Blob API, files will\n            // be uploaded as a whole.\n            maxChunkSize: undefined,\n            // When a non-multipart upload or a chunked multipart upload has been\n            // aborted, this option can be used to resume the upload by setting\n            // it to the size of the already uploaded bytes. This option is most\n            // useful when modifying the options object inside of the \"add\" or\n            // \"send\" callbacks, as the options are cloned for each file upload.\n            uploadedBytes: undefined,\n            // By default, failed (abort or error) file uploads are removed from the\n            // global progress calculation. Set the following option to false to\n            // prevent recalculating the global progress data:\n            recalculateProgress: true,\n            // Interval in milliseconds to calculate and trigger progress events:\n            progressInterval: 100,\n            // Interval in milliseconds to calculate progress bitrate:\n            bitrateInterval: 500,\n            // By default, uploads are started automatically when adding files:\n            autoUpload: true,\n\n            // Error and info messages:\n            messages: {\n                uploadedBytes: 'Uploaded bytes exceed file size'\n            },\n\n            // Translation function, gets the message key to be translated\n            // and an object with context specific data as arguments:\n            i18n: function (message, context) {\n                message = this.messages[message] || message.toString();\n                if (context) {\n                    $.each(context, function (key, value) {\n                        message = message.replace('{' + key + '}', value);\n                    });\n                }\n                return message;\n            },\n\n            // Additional form data to be sent along with the file uploads can be set\n            // using this option, which accepts an array of objects with name and\n            // value properties, a function returning such an array, a FormData\n            // object (for XHR file uploads), or a simple object.\n            // The form of the first fileInput is given as parameter to the function:\n            formData: function (form) {\n                return form.serializeArray();\n            },\n\n            // The add callback is invoked as soon as files are added to the fileupload\n            // widget (via file input selection, drag & drop, paste or add API call).\n            // If the singleFileUploads option is enabled, this callback will be\n            // called once for each file in the selection for XHR file uploads, else\n            // once for each file selection.\n            //\n            // The upload starts when the submit method is invoked on the data parameter.\n            // The data object contains a files property holding the added files\n            // and allows you to override plugin options as well as define ajax settings.\n            //\n            // Listeners for this callback can also be bound the following way:\n            // .bind('fileuploadadd', func);\n            //\n            // data.submit() returns a Promise object and allows to attach additional\n            // handlers using jQuery's Deferred callbacks:\n            // data.submit().done(func).fail(func).always(func);\n            add: function (e, data) {\n                if (data.autoUpload || (data.autoUpload !== false &&\n                        $(this).fileupload('option', 'autoUpload'))) {\n                    data.process().done(function () {\n                        data.submit();\n                    });\n                }\n            },\n\n            // Other callbacks:\n\n            // Callback for the submit event of each file upload:\n            // submit: function (e, data) {}, // .bind('fileuploadsubmit', func);\n\n            // Callback for the start of each file upload request:\n            // send: function (e, data) {}, // .bind('fileuploadsend', func);\n\n            // Callback for successful uploads:\n            // done: function (e, data) {}, // .bind('fileuploaddone', func);\n\n            // Callback for failed (abort or error) uploads:\n            // fail: function (e, data) {}, // .bind('fileuploadfail', func);\n\n            // Callback for completed (success, abort or error) requests:\n            // always: function (e, data) {}, // .bind('fileuploadalways', func);\n\n            // Callback for upload progress events:\n            // progress: function (e, data) {}, // .bind('fileuploadprogress', func);\n\n            // Callback for global upload progress events:\n            // progressall: function (e, data) {}, // .bind('fileuploadprogressall', func);\n\n            // Callback for uploads start, equivalent to the global ajaxStart event:\n            // start: function (e) {}, // .bind('fileuploadstart', func);\n\n            // Callback for uploads stop, equivalent to the global ajaxStop event:\n            // stop: function (e) {}, // .bind('fileuploadstop', func);\n\n            // Callback for change events of the fileInput(s):\n            // change: function (e, data) {}, // .bind('fileuploadchange', func);\n\n            // Callback for paste events to the pasteZone(s):\n            // paste: function (e, data) {}, // .bind('fileuploadpaste', func);\n\n            // Callback for drop events of the dropZone(s):\n            // drop: function (e, data) {}, // .bind('fileuploaddrop', func);\n\n            // Callback for dragover events of the dropZone(s):\n            // dragover: function (e) {}, // .bind('fileuploaddragover', func);\n\n            // Callback for the start of each chunk upload request:\n            // chunksend: function (e, data) {}, // .bind('fileuploadchunksend', func);\n\n            // Callback for successful chunk uploads:\n            // chunkdone: function (e, data) {}, // .bind('fileuploadchunkdone', func);\n\n            // Callback for failed (abort or error) chunk uploads:\n            // chunkfail: function (e, data) {}, // .bind('fileuploadchunkfail', func);\n\n            // Callback for completed (success, abort or error) chunk upload requests:\n            // chunkalways: function (e, data) {}, // .bind('fileuploadchunkalways', func);\n\n            // The plugin options are used as settings object for the ajax calls.\n            // The following are jQuery ajax settings required for the file uploads:\n            processData: false,\n            contentType: false,\n            cache: false\n        },\n\n        // A list of options that require reinitializing event listeners and/or\n        // special initialization code:\n        _specialOptions: [\n            'fileInput',\n            'dropZone',\n            'pasteZone',\n            'multipart',\n            'forceIframeTransport'\n        ],\n\n        _blobSlice: $.support.blobSlice && function () {\n            var slice = this.slice || this.webkitSlice || this.mozSlice;\n            return slice.apply(this, arguments);\n        },\n\n        _BitrateTimer: function () {\n            this.timestamp = ((Date.now) ? Date.now() : (new Date()).getTime());\n            this.loaded = 0;\n            this.bitrate = 0;\n            this.getBitrate = function (now, loaded, interval) {\n                var timeDiff = now - this.timestamp;\n                if (!this.bitrate || !interval || timeDiff > interval) {\n                    this.bitrate = (loaded - this.loaded) * (1000 / timeDiff) * 8;\n                    this.loaded = loaded;\n                    this.timestamp = now;\n                }\n                return this.bitrate;\n            };\n        },\n\n        _isXHRUpload: function (options) {\n            return !options.forceIframeTransport &&\n                ((!options.multipart && $.support.xhrFileUpload) ||\n                $.support.xhrFormDataFileUpload);\n        },\n\n        _getFormData: function (options) {\n            var formData;\n            if (typeof options.formData === 'function') {\n                return options.formData(options.form);\n            }\n            if ($.isArray(options.formData)) {\n                return options.formData;\n            }\n            if ($.type(options.formData) === 'object') {\n                formData = [];\n                $.each(options.formData, function (name, value) {\n                    formData.push({name: name, value: value});\n                });\n                return formData;\n            }\n            return [];\n        },\n\n        _getTotal: function (files) {\n            var total = 0;\n            $.each(files, function (index, file) {\n                total += file.size || 1;\n            });\n            return total;\n        },\n\n        _initProgressObject: function (obj) {\n            var progress = {\n                loaded: 0,\n                total: 0,\n                bitrate: 0\n            };\n            if (obj._progress) {\n                $.extend(obj._progress, progress);\n            } else {\n                obj._progress = progress;\n            }\n        },\n\n        _initResponseObject: function (obj) {\n            var prop;\n            if (obj._response) {\n                for (prop in obj._response) {\n                    if (obj._response.hasOwnProperty(prop)) {\n                        delete obj._response[prop];\n                    }\n                }\n            } else {\n                obj._response = {};\n            }\n        },\n\n        _onProgress: function (e, data) {\n            if (e.lengthComputable) {\n                var now = ((Date.now) ? Date.now() : (new Date()).getTime()),\n                    loaded;\n                if (data._time && data.progressInterval &&\n                        (now - data._time < data.progressInterval) &&\n                        e.loaded !== e.total) {\n                    return;\n                }\n                data._time = now;\n                loaded = Math.floor(\n                    e.loaded / e.total * (data.chunkSize || data._progress.total)\n                ) + (data.uploadedBytes || 0);\n                // Add the difference from the previously loaded state\n                // to the global loaded counter:\n                this._progress.loaded += (loaded - data._progress.loaded);\n                this._progress.bitrate = this._bitrateTimer.getBitrate(\n                    now,\n                    this._progress.loaded,\n                    data.bitrateInterval\n                );\n                data._progress.loaded = data.loaded = loaded;\n                data._progress.bitrate = data.bitrate = data._bitrateTimer.getBitrate(\n                    now,\n                    loaded,\n                    data.bitrateInterval\n                );\n                // Trigger a custom progress event with a total data property set\n                // to the file size(s) of the current upload and a loaded data\n                // property calculated accordingly:\n                this._trigger('progress', e, data);\n                // Trigger a global progress event for all current file uploads,\n                // including ajax calls queued for sequential file uploads:\n                this._trigger('progressall', e, this._progress);\n            }\n        },\n\n        _initProgressListener: function (options) {\n            var that = this,\n                xhr = options.xhr ? options.xhr() : $.ajaxSettings.xhr();\n            // Accesss to the native XHR object is required to add event listeners\n            // for the upload progress event:\n            if (xhr.upload) {\n                $(xhr.upload).bind('progress', function (e) {\n                    var oe = e.originalEvent;\n                    // Make sure the progress event properties get copied over:\n                    e.lengthComputable = oe.lengthComputable;\n                    e.loaded = oe.loaded;\n                    e.total = oe.total;\n                    that._onProgress(e, options);\n                });\n                options.xhr = function () {\n                    return xhr;\n                };\n            }\n        },\n\n        _isInstanceOf: function (type, obj) {\n            // Cross-frame instanceof check\n            return Object.prototype.toString.call(obj) === '[object ' + type + ']';\n        },\n\n        _initXHRData: function (options) {\n            var that = this,\n                formData,\n                file = options.files[0],\n                // Ignore non-multipart setting if not supported:\n                multipart = options.multipart || !$.support.xhrFileUpload,\n                paramName = options.paramName[0];\n            options.headers = options.headers || {};\n            if (options.contentRange) {\n                options.headers['Content-Range'] = options.contentRange;\n            }\n            if (!multipart || options.blob || !this._isInstanceOf('File', file)) {\n                options.headers['Content-Disposition'] = 'attachment; filename=\"' +\n                    encodeURI(file.name) + '\"';\n            }\n            if (!multipart) {\n                options.contentType = file.type;\n                options.data = options.blob || file;\n            } else if ($.support.xhrFormDataFileUpload) {\n                if (options.postMessage) {\n                    // window.postMessage does not allow sending FormData\n                    // objects, so we just add the File/Blob objects to\n                    // the formData array and let the postMessage window\n                    // create the FormData object out of this array:\n                    formData = this._getFormData(options);\n                    if (options.blob) {\n                        formData.push({\n                            name: paramName,\n                            value: options.blob\n                        });\n                    } else {\n                        $.each(options.files, function (index, file) {\n                            formData.push({\n                                name: options.paramName[index] || paramName,\n                                value: file\n                            });\n                        });\n                    }\n                } else {\n                    if (that._isInstanceOf('FormData', options.formData)) {\n                        formData = options.formData;\n                    } else {\n                        formData = new FormData();\n                        $.each(this._getFormData(options), function (index, field) {\n                            formData.append(field.name, field.value);\n                        });\n                    }\n                    if (options.blob) {\n                        formData.append(paramName, options.blob, file.name);\n                    } else {\n                        $.each(options.files, function (index, file) {\n                            // This check allows the tests to run with\n                            // dummy objects:\n                            if (that._isInstanceOf('File', file) ||\n                                    that._isInstanceOf('Blob', file)) {\n                                formData.append(\n                                    options.paramName[index] || paramName,\n                                    file,\n                                    file.name\n                                );\n                            }\n                        });\n                    }\n                }\n                options.data = formData;\n            }\n            // Blob reference is not needed anymore, free memory:\n            options.blob = null;\n        },\n\n        _initIframeSettings: function (options) {\n            var targetHost = $('<a></a>').prop('href', options.url).prop('host');\n            // Setting the dataType to iframe enables the iframe transport:\n            options.dataType = 'iframe ' + (options.dataType || '');\n            // The iframe transport accepts a serialized array as form data:\n            options.formData = this._getFormData(options);\n            // Add redirect url to form data on cross-domain uploads:\n            if (options.redirect && targetHost && targetHost !== location.host) {\n                options.formData.push({\n                    name: options.redirectParamName || 'redirect',\n                    value: options.redirect\n                });\n            }\n        },\n\n        _initDataSettings: function (options) {\n            if (this._isXHRUpload(options)) {\n                if (!this._chunkedUpload(options, true)) {\n                    if (!options.data) {\n                        this._initXHRData(options);\n                    }\n                    this._initProgressListener(options);\n                }\n                if (options.postMessage) {\n                    // Setting the dataType to postmessage enables the\n                    // postMessage transport:\n                    options.dataType = 'postmessage ' + (options.dataType || '');\n                }\n            } else {\n                this._initIframeSettings(options);\n            }\n        },\n\n        _getParamName: function (options) {\n            var fileInput = $(options.fileInput),\n                paramName = options.paramName;\n            if (!paramName) {\n                paramName = [];\n                fileInput.each(function () {\n                    var input = $(this),\n                        name = input.prop('name') || 'files[]',\n                        i = (input.prop('files') || [1]).length;\n                    while (i) {\n                        paramName.push(name);\n                        i -= 1;\n                    }\n                });\n                if (!paramName.length) {\n                    paramName = [fileInput.prop('name') || 'files[]'];\n                }\n            } else if (!$.isArray(paramName)) {\n                paramName = [paramName];\n            }\n            return paramName;\n        },\n\n        _initFormSettings: function (options) {\n            // Retrieve missing options from the input field and the\n            // associated form, if available:\n            if (!options.form || !options.form.length) {\n                options.form = $(options.fileInput.prop('form'));\n                // If the given file input doesn't have an associated form,\n                // use the default widget file input's form:\n                if (!options.form.length) {\n                    options.form = $(this.options.fileInput.prop('form'));\n                }\n            }\n            options.paramName = this._getParamName(options);\n            if (!options.url) {\n                options.url = options.form.prop('action') || location.href;\n            }\n            // The HTTP request method must be \"POST\" or \"PUT\":\n            options.type = (options.type || options.form.prop('method') || '')\n                .toUpperCase();\n            if (options.type !== 'POST' && options.type !== 'PUT' &&\n                    options.type !== 'PATCH') {\n                options.type = 'POST';\n            }\n            if (!options.formAcceptCharset) {\n                options.formAcceptCharset = options.form.attr('accept-charset');\n            }\n        },\n\n        _getAJAXSettings: function (data) {\n            var options = $.extend({}, this.options, data);\n            this._initFormSettings(options);\n            this._initDataSettings(options);\n            return options;\n        },\n\n        // jQuery 1.6 doesn't provide .state(),\n        // while jQuery 1.8+ removed .isRejected() and .isResolved():\n        _getDeferredState: function (deferred) {\n            if (deferred.state) {\n                return deferred.state();\n            }\n            if (deferred.isResolved()) {\n                return 'resolved';\n            }\n            if (deferred.isRejected()) {\n                return 'rejected';\n            }\n            return 'pending';\n        },\n\n        // Maps jqXHR callbacks to the equivalent\n        // methods of the given Promise object:\n        _enhancePromise: function (promise) {\n            promise.success = promise.done;\n            promise.error = promise.fail;\n            promise.complete = promise.always;\n            return promise;\n        },\n\n        // Creates and returns a Promise object enhanced with\n        // the jqXHR methods abort, success, error and complete:\n        _getXHRPromise: function (resolveOrReject, context, args) {\n            var dfd = $.Deferred(),\n                promise = dfd.promise();\n            context = context || this.options.context || promise;\n            if (resolveOrReject === true) {\n                dfd.resolveWith(context, args);\n            } else if (resolveOrReject === false) {\n                dfd.rejectWith(context, args);\n            }\n            promise.abort = dfd.promise;\n            return this._enhancePromise(promise);\n        },\n\n        // Adds convenience methods to the data callback argument:\n        _addConvenienceMethods: function (e, data) {\n            var that = this,\n                getPromise = function (data) {\n                    return $.Deferred().resolveWith(that, [data]).promise();\n                };\n            data.process = function (resolveFunc, rejectFunc) {\n                if (resolveFunc || rejectFunc) {\n                    data._processQueue = this._processQueue =\n                        (this._processQueue || getPromise(this))\n                            .pipe(resolveFunc, rejectFunc);\n                }\n                return this._processQueue || getPromise(this);\n            };\n            data.submit = function () {\n                if (this.state() !== 'pending') {\n                    data.jqXHR = this.jqXHR =\n                        (that._trigger('submit', e, this) !== false) &&\n                        that._onSend(e, this);\n                }\n                return this.jqXHR || that._getXHRPromise();\n            };\n            data.abort = function () {\n                if (this.jqXHR) {\n                    return this.jqXHR.abort();\n                }\n                return that._getXHRPromise();\n            };\n            data.state = function () {\n                if (this.jqXHR) {\n                    return that._getDeferredState(this.jqXHR);\n                }\n                if (this._processQueue) {\n                    return that._getDeferredState(this._processQueue);\n                }\n            };\n            data.progress = function () {\n                return this._progress;\n            };\n            data.response = function () {\n                return this._response;\n            };\n        },\n\n        // Parses the Range header from the server response\n        // and returns the uploaded bytes:\n        _getUploadedBytes: function (jqXHR) {\n            var range = jqXHR.getResponseHeader('Range'),\n                parts = range && range.split('-'),\n                upperBytesPos = parts && parts.length > 1 &&\n                    parseInt(parts[1], 10);\n            return upperBytesPos && upperBytesPos + 1;\n        },\n\n        // Uploads a file in multiple, sequential requests\n        // by splitting the file up in multiple blob chunks.\n        // If the second parameter is true, only tests if the file\n        // should be uploaded in chunks, but does not invoke any\n        // upload requests:\n        _chunkedUpload: function (options, testOnly) {\n            options.uploadedBytes = options.uploadedBytes || 0;\n            var that = this,\n                file = options.files[0],\n                fs = file.size,\n                ub = options.uploadedBytes,\n                mcs = options.maxChunkSize || fs,\n                slice = this._blobSlice,\n                dfd = $.Deferred(),\n                promise = dfd.promise(),\n                jqXHR,\n                upload;\n            if (!(this._isXHRUpload(options) && slice && (ub || mcs < fs)) ||\n                    options.data) {\n                return false;\n            }\n            if (testOnly) {\n                return true;\n            }\n            if (ub >= fs) {\n                file.error = options.i18n('uploadedBytes');\n                return this._getXHRPromise(\n                    false,\n                    options.context,\n                    [null, 'error', file.error]\n                );\n            }\n            // The chunk upload method:\n            upload = function () {\n                // Clone the options object for each chunk upload:\n                var o = $.extend({}, options),\n                    currentLoaded = o._progress.loaded;\n                o.blob = slice.call(\n                    file,\n                    ub,\n                    ub + mcs,\n                    file.type\n                );\n                // Store the current chunk size, as the blob itself\n                // will be dereferenced after data processing:\n                o.chunkSize = o.blob.size;\n                // Expose the chunk bytes position range:\n                o.contentRange = 'bytes ' + ub + '-' +\n                    (ub + o.chunkSize - 1) + '/' + fs;\n                // Process the upload data (the blob and potential form data):\n                that._initXHRData(o);\n                // Add progress listeners for this chunk upload:\n                that._initProgressListener(o);\n                jqXHR = ((that._trigger('chunksend', null, o) !== false && $.ajax(o)) ||\n                        that._getXHRPromise(false, o.context))\n                    .done(function (result, textStatus, jqXHR) {\n                        ub = that._getUploadedBytes(jqXHR) ||\n                            (ub + o.chunkSize);\n                        // Create a progress event if no final progress event\n                        // with loaded equaling total has been triggered\n                        // for this chunk:\n                        if (currentLoaded + o.chunkSize - o._progress.loaded) {\n                            that._onProgress($.Event('progress', {\n                                lengthComputable: true,\n                                loaded: ub - o.uploadedBytes,\n                                total: ub - o.uploadedBytes\n                            }), o);\n                        }\n                        options.uploadedBytes = o.uploadedBytes = ub;\n                        o.result = result;\n                        o.textStatus = textStatus;\n                        o.jqXHR = jqXHR;\n                        that._trigger('chunkdone', null, o);\n                        that._trigger('chunkalways', null, o);\n                        if (ub < fs) {\n                            // File upload not yet complete,\n                            // continue with the next chunk:\n                            upload();\n                        } else {\n                            dfd.resolveWith(\n                                o.context,\n                                [result, textStatus, jqXHR]\n                            );\n                        }\n                    })\n                    .fail(function (jqXHR, textStatus, errorThrown) {\n                        o.jqXHR = jqXHR;\n                        o.textStatus = textStatus;\n                        o.errorThrown = errorThrown;\n                        that._trigger('chunkfail', null, o);\n                        that._trigger('chunkalways', null, o);\n                        dfd.rejectWith(\n                            o.context,\n                            [jqXHR, textStatus, errorThrown]\n                        );\n                    });\n            };\n            this._enhancePromise(promise);\n            promise.abort = function () {\n                return jqXHR.abort();\n            };\n            upload();\n            return promise;\n        },\n\n        _beforeSend: function (e, data) {\n            if (this._active === 0) {\n                // the start callback is triggered when an upload starts\n                // and no other uploads are currently running,\n                // equivalent to the global ajaxStart event:\n                this._trigger('start');\n                // Set timer for global bitrate progress calculation:\n                this._bitrateTimer = new this._BitrateTimer();\n                // Reset the global progress values:\n                this._progress.loaded = this._progress.total = 0;\n                this._progress.bitrate = 0;\n            }\n            // Make sure the container objects for the .response() and\n            // .progress() methods on the data object are available\n            // and reset to their initial state:\n            this._initResponseObject(data);\n            this._initProgressObject(data);\n            data._progress.loaded = data.loaded = data.uploadedBytes || 0;\n            data._progress.total = data.total = this._getTotal(data.files) || 1;\n            data._progress.bitrate = data.bitrate = 0;\n            this._active += 1;\n            // Initialize the global progress values:\n            this._progress.loaded += data.loaded;\n            this._progress.total += data.total;\n        },\n\n        _onDone: function (result, textStatus, jqXHR, options) {\n            var total = options._progress.total,\n                response = options._response;\n            if (options._progress.loaded < total) {\n                // Create a progress event if no final progress event\n                // with loaded equaling total has been triggered:\n                this._onProgress($.Event('progress', {\n                    lengthComputable: true,\n                    loaded: total,\n                    total: total\n                }), options);\n            }\n            response.result = options.result = result;\n            response.textStatus = options.textStatus = textStatus;\n            response.jqXHR = options.jqXHR = jqXHR;\n            this._trigger('done', null, options);\n        },\n\n        _onFail: function (jqXHR, textStatus, errorThrown, options) {\n            var response = options._response;\n            if (options.recalculateProgress) {\n                // Remove the failed (error or abort) file upload from\n                // the global progress calculation:\n                this._progress.loaded -= options._progress.loaded;\n                this._progress.total -= options._progress.total;\n            }\n            response.jqXHR = options.jqXHR = jqXHR;\n            response.textStatus = options.textStatus = textStatus;\n            response.errorThrown = options.errorThrown = errorThrown;\n            this._trigger('fail', null, options);\n        },\n\n        _onAlways: function (jqXHRorResult, textStatus, jqXHRorError, options) {\n            // jqXHRorResult, textStatus and jqXHRorError are added to the\n            // options object via done and fail callbacks\n            this._trigger('always', null, options);\n        },\n\n        _onSend: function (e, data) {\n            if (!data.submit) {\n                this._addConvenienceMethods(e, data);\n            }\n            var that = this,\n                jqXHR,\n                aborted,\n                slot,\n                pipe,\n                options = that._getAJAXSettings(data),\n                send = function () {\n                    that._sending += 1;\n                    // Set timer for bitrate progress calculation:\n                    options._bitrateTimer = new that._BitrateTimer();\n                    jqXHR = jqXHR || (\n                        ((aborted || that._trigger('send', e, options) === false) &&\n                        that._getXHRPromise(false, options.context, aborted)) ||\n                        that._chunkedUpload(options) || $.ajax(options)\n                    ).done(function (result, textStatus, jqXHR) {\n                        that._onDone(result, textStatus, jqXHR, options);\n                    }).fail(function (jqXHR, textStatus, errorThrown) {\n                        that._onFail(jqXHR, textStatus, errorThrown, options);\n                    }).always(function (jqXHRorResult, textStatus, jqXHRorError) {\n                        that._onAlways(\n                            jqXHRorResult,\n                            textStatus,\n                            jqXHRorError,\n                            options\n                        );\n                        that._sending -= 1;\n                        that._active -= 1;\n                        if (options.limitConcurrentUploads &&\n                                options.limitConcurrentUploads > that._sending) {\n                            // Start the next queued upload,\n                            // that has not been aborted:\n                            var nextSlot = that._slots.shift();\n                            while (nextSlot) {\n                                if (that._getDeferredState(nextSlot) === 'pending') {\n                                    nextSlot.resolve();\n                                    break;\n                                }\n                                nextSlot = that._slots.shift();\n                            }\n                        }\n                        if (that._active === 0) {\n                            // The stop callback is triggered when all uploads have\n                            // been completed, equivalent to the global ajaxStop event:\n                            that._trigger('stop');\n                        }\n                    });\n                    return jqXHR;\n                };\n            this._beforeSend(e, options);\n            if (this.options.sequentialUploads ||\n                    (this.options.limitConcurrentUploads &&\n                    this.options.limitConcurrentUploads <= this._sending)) {\n                if (this.options.limitConcurrentUploads > 1) {\n                    slot = $.Deferred();\n                    this._slots.push(slot);\n                    pipe = slot.pipe(send);\n                } else {\n                    this._sequence = this._sequence.pipe(send, send);\n                    pipe = this._sequence;\n                }\n                // Return the piped Promise object, enhanced with an abort method,\n                // which is delegated to the jqXHR object of the current upload,\n                // and jqXHR callbacks mapped to the equivalent Promise methods:\n                pipe.abort = function () {\n                    aborted = [undefined, 'abort', 'abort'];\n                    if (!jqXHR) {\n                        if (slot) {\n                            slot.rejectWith(options.context, aborted);\n                        }\n                        return send();\n                    }\n                    return jqXHR.abort();\n                };\n                return this._enhancePromise(pipe);\n            }\n            return send();\n        },\n\n        _onAdd: function (e, data) {\n            var that = this,\n                result = true,\n                options = $.extend({}, this.options, data),\n                limit = options.limitMultiFileUploads,\n                paramName = this._getParamName(options),\n                paramNameSet,\n                paramNameSlice,\n                fileSet,\n                i;\n            if (!(options.singleFileUploads || limit) ||\n                    !this._isXHRUpload(options)) {\n                fileSet = [data.files];\n                paramNameSet = [paramName];\n            } else if (!options.singleFileUploads && limit) {\n                fileSet = [];\n                paramNameSet = [];\n                for (i = 0; i < data.files.length; i += limit) {\n                    fileSet.push(data.files.slice(i, i + limit));\n                    paramNameSlice = paramName.slice(i, i + limit);\n                    if (!paramNameSlice.length) {\n                        paramNameSlice = paramName;\n                    }\n                    paramNameSet.push(paramNameSlice);\n                }\n            } else {\n                paramNameSet = paramName;\n            }\n            data.originalFiles = data.files;\n            $.each(fileSet || data.files, function (index, element) {\n                var newData = $.extend({}, data);\n                newData.files = fileSet ? element : [element];\n                newData.paramName = paramNameSet[index];\n                that._initResponseObject(newData);\n                that._initProgressObject(newData);\n                that._addConvenienceMethods(e, newData);\n                result = that._trigger('add', e, newData);\n                return result;\n            });\n            return result;\n        },\n\n        _replaceFileInput: function (input) {\n            var inputClone = input.clone(true);\n            $('<form></form>').append(inputClone)[0].reset();\n            // Detaching allows to insert the fileInput on another form\n            // without loosing the file input value:\n            input.after(inputClone).detach();\n            // Avoid memory leaks with the detached file input:\n            $.cleanData(input.unbind('remove'));\n            // Replace the original file input element in the fileInput\n            // elements set with the clone, which has been copied including\n            // event handlers:\n            this.options.fileInput = this.options.fileInput.map(function (i, el) {\n                if (el === input[0]) {\n                    return inputClone[0];\n                }\n                return el;\n            });\n            // If the widget has been initialized on the file input itself,\n            // override this.element with the file input clone:\n            if (input[0] === this.element[0]) {\n                this.element = inputClone;\n            }\n        },\n\n        _handleFileTreeEntry: function (entry, path) {\n            var that = this,\n                dfd = $.Deferred(),\n                errorHandler = function (e) {\n                    if (e && !e.entry) {\n                        e.entry = entry;\n                    }\n                    // Since $.when returns immediately if one\n                    // Deferred is rejected, we use resolve instead.\n                    // This allows valid files and invalid items\n                    // to be returned together in one set:\n                    dfd.resolve([e]);\n                },\n                dirReader;\n            path = path || '';\n            if (entry.isFile) {\n                if (entry._file) {\n                    // Workaround for Chrome bug #149735\n                    entry._file.relativePath = path;\n                    dfd.resolve(entry._file);\n                } else {\n                    entry.file(function (file) {\n                        file.relativePath = path;\n                        dfd.resolve(file);\n                    }, errorHandler);\n                }\n            } else if (entry.isDirectory) {\n                dirReader = entry.createReader();\n                dirReader.readEntries(function (entries) {\n                    that._handleFileTreeEntries(\n                        entries,\n                        path + entry.name + '/'\n                    ).done(function (files) {\n                        dfd.resolve(files);\n                    }).fail(errorHandler);\n                }, errorHandler);\n            } else {\n                // Return an empy list for file system items\n                // other than files or directories:\n                dfd.resolve([]);\n            }\n            return dfd.promise();\n        },\n\n        _handleFileTreeEntries: function (entries, path) {\n            var that = this;\n            return $.when.apply(\n                $,\n                $.map(entries, function (entry) {\n                    return that._handleFileTreeEntry(entry, path);\n                })\n            ).pipe(function () {\n                return Array.prototype.concat.apply(\n                    [],\n                    arguments\n                );\n            });\n        },\n\n        _getDroppedFiles: function (dataTransfer) {\n            dataTransfer = dataTransfer || {};\n            var items = dataTransfer.items;\n            if (items && items.length && (items[0].webkitGetAsEntry ||\n                    items[0].getAsEntry)) {\n                return this._handleFileTreeEntries(\n                    $.map(items, function (item) {\n                        var entry;\n                        if (item.webkitGetAsEntry) {\n                            entry = item.webkitGetAsEntry();\n                            if (entry) {\n                                // Workaround for Chrome bug #149735:\n                                entry._file = item.getAsFile();\n                            }\n                            return entry;\n                        }\n                        return item.getAsEntry();\n                    })\n                );\n            }\n            return $.Deferred().resolve(\n                $.makeArray(dataTransfer.files)\n            ).promise();\n        },\n\n        _getSingleFileInputFiles: function (fileInput) {\n            fileInput = $(fileInput);\n            var entries = fileInput.prop('webkitEntries') ||\n                    fileInput.prop('entries'),\n                files,\n                value;\n            if (entries && entries.length) {\n                return this._handleFileTreeEntries(entries);\n            }\n            files = $.makeArray(fileInput.prop('files'));\n            if (!files.length) {\n                value = fileInput.prop('value');\n                if (!value) {\n                    return $.Deferred().resolve([]).promise();\n                }\n                // If the files property is not available, the browser does not\n                // support the File API and we add a pseudo File object with\n                // the input value as name with path information removed:\n                files = [{name: value.replace(/^.*\\\\/, '')}];\n            } else if (files[0].name === undefined && files[0].fileName) {\n                // File normalization for Safari 4 and Firefox 3:\n                $.each(files, function (index, file) {\n                    file.name = file.fileName;\n                    file.size = file.fileSize;\n                });\n            }\n            return $.Deferred().resolve(files).promise();\n        },\n\n        _getFileInputFiles: function (fileInput) {\n            if (!(fileInput instanceof $) || fileInput.length === 1) {\n                return this._getSingleFileInputFiles(fileInput);\n            }\n            return $.when.apply(\n                $,\n                $.map(fileInput, this._getSingleFileInputFiles)\n            ).pipe(function () {\n                return Array.prototype.concat.apply(\n                    [],\n                    arguments\n                );\n            });\n        },\n\n        _onChange: function (e) {\n            var that = this,\n                data = {\n                    fileInput: $(e.target),\n                    form: $(e.target.form)\n                };\n            this._getFileInputFiles(data.fileInput).always(function (files) {\n                data.files = files;\n                if (that.options.replaceFileInput) {\n                    that._replaceFileInput(data.fileInput);\n                }\n                if (that._trigger('change', e, data) !== false) {\n                    that._onAdd(e, data);\n                }\n            });\n        },\n\n        _onPaste: function (e) {\n            var items = e.originalEvent && e.originalEvent.clipboardData &&\n                    e.originalEvent.clipboardData.items,\n                data = {files: []};\n            if (items && items.length) {\n                $.each(items, function (index, item) {\n                    var file = item.getAsFile && item.getAsFile();\n                    if (file) {\n                        data.files.push(file);\n                    }\n                });\n                if (this._trigger('paste', e, data) === false ||\n                        this._onAdd(e, data) === false) {\n                    return false;\n                }\n            }\n        },\n\n        _onDrop: function (e) {\n            e.dataTransfer = e.originalEvent && e.originalEvent.dataTransfer;\n            var that = this,\n                dataTransfer = e.dataTransfer,\n                data = {};\n            if (dataTransfer && dataTransfer.files && dataTransfer.files.length) {\n                e.preventDefault();\n                this._getDroppedFiles(dataTransfer).always(function (files) {\n                    data.files = files;\n                    if (that._trigger('drop', e, data) !== false) {\n                        that._onAdd(e, data);\n                    }\n                });\n            }\n        },\n\n        _onDragOver: function (e) {\n            e.dataTransfer = e.originalEvent && e.originalEvent.dataTransfer;\n            var dataTransfer = e.dataTransfer;\n            if (dataTransfer) {\n                if (this._trigger('dragover', e) === false) {\n                    return false;\n                }\n                if ($.inArray('Files', dataTransfer.types) !== -1) {\n                    dataTransfer.dropEffect = 'copy';\n                    e.preventDefault();\n                }\n            }\n        },\n\n        _initEventHandlers: function () {\n            if (this._isXHRUpload(this.options)) {\n                this._on(this.options.dropZone, {\n                    dragover: this._onDragOver,\n                    drop: this._onDrop\n                });\n                this._on(this.options.pasteZone, {\n                    paste: this._onPaste\n                });\n            }\n            this._on(this.options.fileInput, {\n                change: this._onChange\n            });\n        },\n\n        _destroyEventHandlers: function () {\n            this._off(this.options.dropZone, 'dragover drop');\n            this._off(this.options.pasteZone, 'paste');\n            this._off(this.options.fileInput, 'change');\n        },\n\n        _setOption: function (key, value) {\n            var reinit = $.inArray(key, this._specialOptions) !== -1;\n            if (reinit) {\n                this._destroyEventHandlers();\n            }\n            this._super(key, value);\n            if (reinit) {\n                this._initSpecialOptions();\n                this._initEventHandlers();\n            }\n        },\n\n        _initSpecialOptions: function () {\n            var options = this.options;\n            if (options.fileInput === undefined) {\n                options.fileInput = this.element.is('input[type=\"file\"]') ?\n                        this.element : this.element.find('input[type=\"file\"]');\n            } else if (!(options.fileInput instanceof $)) {\n                options.fileInput = $(options.fileInput);\n            }\n            if (!(options.dropZone instanceof $)) {\n                options.dropZone = $(options.dropZone);\n            }\n            if (!(options.pasteZone instanceof $)) {\n                options.pasteZone = $(options.pasteZone);\n            }\n        },\n\n        _getRegExp: function (str) {\n            var parts = str.split('/'),\n                modifiers = parts.pop();\n            parts.shift();\n            return new RegExp(parts.join('/'), modifiers);\n        },\n\n        _isRegExpOption: function (key, value) {\n            return key !== 'url' && $.type(value) === 'string' &&\n                /^\\/.*\\/[igm]{0,3}$/.test(value);\n        },\n\n        _initDataAttributes: function () {\n            var that = this,\n                options = this.options;\n            // Initialize options set via HTML5 data-attributes:\n            $.each(\n                $(this.element[0].cloneNode(false)).data(),\n                function (key, value) {\n                    if (that._isRegExpOption(key, value)) {\n                        value = that._getRegExp(value);\n                    }\n                    options[key] = value;\n                }\n            );\n        },\n\n        _create: function () {\n            this._initDataAttributes();\n            this._initSpecialOptions();\n            this._slots = [];\n            this._sequence = this._getXHRPromise(true);\n            this._sending = this._active = 0;\n            this._initProgressObject(this);\n            this._initEventHandlers();\n        },\n\n        // This method is exposed to the widget API and allows to query\n        // the number of active uploads:\n        active: function () {\n            return this._active;\n        },\n\n        // This method is exposed to the widget API and allows to query\n        // the widget upload progress.\n        // It returns an object with loaded, total and bitrate properties\n        // for the running uploads:\n        progress: function () {\n            return this._progress;\n        },\n\n        // This method is exposed to the widget API and allows adding files\n        // using the fileupload API. The data parameter accepts an object which\n        // must have a files property and can contain additional options:\n        // .fileupload('add', {files: filesList});\n        add: function (data) {\n            var that = this;\n            if (!data || this.options.disabled) {\n                return;\n            }\n            if (data.fileInput && !data.files) {\n                this._getFileInputFiles(data.fileInput).always(function (files) {\n                    data.files = files;\n                    that._onAdd(null, data);\n                });\n            } else {\n                data.files = $.makeArray(data.files);\n                this._onAdd(null, data);\n            }\n        },\n\n        // This method is exposed to the widget API and allows sending files\n        // using the fileupload API. The data parameter accepts an object which\n        // must have a files or fileInput property and can contain additional options:\n        // .fileupload('send', {files: filesList});\n        // The method returns a Promise object for the file upload call.\n        send: function (data) {\n            if (data && !this.options.disabled) {\n                if (data.fileInput && !data.files) {\n                    var that = this,\n                        dfd = $.Deferred(),\n                        promise = dfd.promise(),\n                        jqXHR,\n                        aborted;\n                    promise.abort = function () {\n                        aborted = true;\n                        if (jqXHR) {\n                            return jqXHR.abort();\n                        }\n                        dfd.reject(null, 'abort', 'abort');\n                        return promise;\n                    };\n                    this._getFileInputFiles(data.fileInput).always(\n                        function (files) {\n                            if (aborted) {\n                                return;\n                            }\n                            data.files = files;\n                            jqXHR = that._onSend(null, data).then(\n                                function (result, textStatus, jqXHR) {\n                                    dfd.resolve(result, textStatus, jqXHR);\n                                },\n                                function (jqXHR, textStatus, errorThrown) {\n                                    dfd.reject(jqXHR, textStatus, errorThrown);\n                                }\n                            );\n                        }\n                    );\n                    return this._enhancePromise(promise);\n                }\n                data.files = $.makeArray(data.files);\n                if (data.files.length) {\n                    return this._onSend(null, data);\n                }\n            }\n            return this._getXHRPromise(false, data && data.context);\n        }\n\n    });\n\n}));\n"
  },
  {
    "path": "samples/photo_album/src/main/webapp/assets/javascripts/cloudinary/jquery.iframe-transport.js",
    "content": "/*\n * jQuery Iframe Transport Plugin 1.7\n * https://github.com/blueimp/jQuery-File-Upload\n *\n * Copyright 2011, Sebastian Tschan\n * https://blueimp.net\n *\n * Licensed under the MIT license:\n * http://www.opensource.org/licenses/MIT\n */\n\n/*jslint unparam: true, nomen: true */\n/*global define, window, document */\n\n(function (factory) {\n    'use strict';\n    if (typeof define === 'function' && define.amd) {\n        // Register as an anonymous AMD module:\n        define(['jquery'], factory);\n    } else {\n        // Browser globals:\n        factory(window.jQuery);\n    }\n}(function ($) {\n    'use strict';\n\n    // Helper variable to create unique names for the transport iframes:\n    var counter = 0;\n\n    // The iframe transport accepts three additional options:\n    // options.fileInput: a jQuery collection of file input fields\n    // options.paramName: the parameter name for the file form data,\n    //  overrides the name property of the file input field(s),\n    //  can be a string or an array of strings.\n    // options.formData: an array of objects with name and value properties,\n    //  equivalent to the return data of .serializeArray(), e.g.:\n    //  [{name: 'a', value: 1}, {name: 'b', value: 2}]\n    $.ajaxTransport('iframe', function (options) {\n        if (options.async) {\n            var form,\n                iframe,\n                addParamChar;\n            return {\n                send: function (_, completeCallback) {\n                    form = $('<form style=\"display:none;\"></form>');\n                    form.attr('accept-charset', options.formAcceptCharset);\n                    addParamChar = /\\?/.test(options.url) ? '&' : '?';\n                    // XDomainRequest only supports GET and POST:\n                    if (options.type === 'DELETE') {\n                        options.url = options.url + addParamChar + '_method=DELETE';\n                        options.type = 'POST';\n                    } else if (options.type === 'PUT') {\n                        options.url = options.url + addParamChar + '_method=PUT';\n                        options.type = 'POST';\n                    } else if (options.type === 'PATCH') {\n                        options.url = options.url + addParamChar + '_method=PATCH';\n                        options.type = 'POST';\n                    }\n                    // javascript:false as initial iframe src\n                    // prevents warning popups on HTTPS in IE6.\n                    // IE versions below IE8 cannot set the name property of\n                    // elements that have already been added to the DOM,\n                    // so we set the name along with the iframe HTML markup:\n                    counter += 1;\n                    iframe = $(\n                        '<iframe src=\"javascript:false;\" name=\"iframe-transport-' +\n                            counter + '\"></iframe>'\n                    ).bind('load', function () {\n                        var fileInputClones,\n                            paramNames = $.isArray(options.paramName) ?\n                                    options.paramName : [options.paramName];\n                        iframe\n                            .unbind('load')\n                            .bind('load', function () {\n                                var response;\n                                // Wrap in a try/catch block to catch exceptions thrown\n                                // when trying to access cross-domain iframe contents:\n                                try {\n                                    response = iframe.contents();\n                                    // Google Chrome and Firefox do not throw an\n                                    // exception when calling iframe.contents() on\n                                    // cross-domain requests, so we unify the response:\n                                    if (!response.length || !response[0].firstChild) {\n                                        throw new Error();\n                                    }\n                                } catch (e) {\n                                    response = undefined;\n                                }\n                                // The complete callback returns the\n                                // iframe content document as response object:\n                                completeCallback(\n                                    200,\n                                    'success',\n                                    {'iframe': response}\n                                );\n                                // Fix for IE endless progress bar activity bug\n                                // (happens on form submits to iframe targets):\n                                $('<iframe src=\"javascript:false;\"></iframe>')\n                                    .appendTo(form);\n                                window.setTimeout(function () {\n                                    // Removing the form in a setTimeout call\n                                    // allows Chrome's developer tools to display\n                                    // the response result\n                                    form.remove();\n                                }, 0);\n                            });\n                        form\n                            .prop('target', iframe.prop('name'))\n                            .prop('action', options.url)\n                            .prop('method', options.type);\n                        if (options.formData) {\n                            $.each(options.formData, function (index, field) {\n                                $('<input type=\"hidden\"/>')\n                                    .prop('name', field.name)\n                                    .val(field.value)\n                                    .appendTo(form);\n                            });\n                        }\n                        if (options.fileInput && options.fileInput.length &&\n                                options.type === 'POST') {\n                            fileInputClones = options.fileInput.clone();\n                            // Insert a clone for each file input field:\n                            options.fileInput.after(function (index) {\n                                return fileInputClones[index];\n                            });\n                            if (options.paramName) {\n                                options.fileInput.each(function (index) {\n                                    $(this).prop(\n                                        'name',\n                                        paramNames[index] || options.paramName\n                                    );\n                                });\n                            }\n                            // Appending the file input fields to the hidden form\n                            // removes them from their original location:\n                            form\n                                .append(options.fileInput)\n                                .prop('enctype', 'multipart/form-data')\n                                // enctype must be set as encoding for IE:\n                                .prop('encoding', 'multipart/form-data');\n                        }\n                        form.submit();\n                        // Insert the file input fields at their original location\n                        // by replacing the clones with the originals:\n                        if (fileInputClones && fileInputClones.length) {\n                            options.fileInput.each(function (index, input) {\n                                var clone = $(fileInputClones[index]);\n                                $(input).prop('name', clone.prop('name'));\n                                clone.replaceWith(input);\n                            });\n                        }\n                    });\n                    form.append(iframe).appendTo(document.body);\n                },\n                abort: function () {\n                    if (iframe) {\n                        // javascript:false as iframe src aborts the request\n                        // and prevents warning popups on HTTPS in IE6.\n                        // concat is used to avoid the \"Script URL\" JSLint error:\n                        iframe\n                            .unbind('load')\n                            .prop('src', 'javascript'.concat(':false;'));\n                    }\n                    if (form) {\n                        form.remove();\n                    }\n                }\n            };\n        }\n    });\n\n    // The iframe transport returns the iframe content document as response.\n    // The following adds converters from iframe to text, json, html, xml\n    // and script.\n    // Please note that the Content-Type for JSON responses has to be text/plain\n    // or text/html, if the browser doesn't include application/json in the\n    // Accept header, else IE will show a download dialog.\n    // The Content-Type for XML responses on the other hand has to be always\n    // application/xml or text/xml, so IE properly parses the XML response.\n    // See also\n    // https://github.com/blueimp/jQuery-File-Upload/wiki/Setup#content-type-negotiation\n    $.ajaxSetup({\n        converters: {\n            'iframe text': function (iframe) {\n                return iframe && $(iframe[0].body).text();\n            },\n            'iframe json': function (iframe) {\n                return iframe && $.parseJSON($(iframe[0].body).text());\n            },\n            'iframe html': function (iframe) {\n                return iframe && $(iframe[0].body).html();\n            },\n            'iframe xml': function (iframe) {\n                var xmlDoc = iframe && iframe[0];\n                return xmlDoc && $.isXMLDoc(xmlDoc) ? xmlDoc :\n                        $.parseXML((xmlDoc.XMLDocument && xmlDoc.XMLDocument.xml) ||\n                            $(xmlDoc.body).html());\n            },\n            'iframe script': function (iframe) {\n                return iframe && $.globalEval($(iframe[0].body).text());\n            }\n        }\n    });\n\n}));\n"
  },
  {
    "path": "samples/photo_album/src/main/webapp/assets/javascripts/cloudinary/jquery.ui.widget.js",
    "content": "/*\n * jQuery UI Widget 1.10.3+amd\n * https://github.com/blueimp/jQuery-File-Upload\n *\n * Copyright 2013 jQuery Foundation and other contributors\n * Released under the MIT license.\n * http://jquery.org/license\n *\n * http://api.jqueryui.com/jQuery.widget/\n */\n\n(function (factory) {\n    if (typeof define === \"function\" && define.amd) {\n        // Register as an anonymous AMD module:\n        define([\"jquery\"], factory);\n    } else {\n        // Browser globals:\n        factory(jQuery);\n    }\n}(function( $, undefined ) {\n\nvar uuid = 0,\n\tslice = Array.prototype.slice,\n\t_cleanData = $.cleanData;\n$.cleanData = function( elems ) {\n\tfor ( var i = 0, elem; (elem = elems[i]) != null; i++ ) {\n\t\ttry {\n\t\t\t$( elem ).triggerHandler( \"remove\" );\n\t\t// http://bugs.jquery.com/ticket/8235\n\t\t} catch( e ) {}\n\t}\n\t_cleanData( elems );\n};\n\n$.widget = function( name, base, prototype ) {\n\tvar fullName, existingConstructor, constructor, basePrototype,\n\t\t// proxiedPrototype allows the provided prototype to remain unmodified\n\t\t// so that it can be used as a mixin for multiple widgets (#8876)\n\t\tproxiedPrototype = {},\n\t\tnamespace = name.split( \".\" )[ 0 ];\n\n\tname = name.split( \".\" )[ 1 ];\n\tfullName = namespace + \"-\" + name;\n\n\tif ( !prototype ) {\n\t\tprototype = base;\n\t\tbase = $.Widget;\n\t}\n\n\t// create selector for plugin\n\t$.expr[ \":\" ][ fullName.toLowerCase() ] = function( elem ) {\n\t\treturn !!$.data( elem, fullName );\n\t};\n\n\t$[ namespace ] = $[ namespace ] || {};\n\texistingConstructor = $[ namespace ][ name ];\n\tconstructor = $[ namespace ][ name ] = function( options, element ) {\n\t\t// allow instantiation without \"new\" keyword\n\t\tif ( !this._createWidget ) {\n\t\t\treturn new constructor( options, element );\n\t\t}\n\n\t\t// allow instantiation without initializing for simple inheritance\n\t\t// must use \"new\" keyword (the code above always passes args)\n\t\tif ( arguments.length ) {\n\t\t\tthis._createWidget( options, element );\n\t\t}\n\t};\n\t// extend with the existing constructor to carry over any static properties\n\t$.extend( constructor, existingConstructor, {\n\t\tversion: prototype.version,\n\t\t// copy the object used to create the prototype in case we need to\n\t\t// redefine the widget later\n\t\t_proto: $.extend( {}, prototype ),\n\t\t// track widgets that inherit from this widget in case this widget is\n\t\t// redefined after a widget inherits from it\n\t\t_childConstructors: []\n\t});\n\n\tbasePrototype = new base();\n\t// we need to make the options hash a property directly on the new instance\n\t// otherwise we'll modify the options hash on the prototype that we're\n\t// inheriting from\n\tbasePrototype.options = $.widget.extend( {}, basePrototype.options );\n\t$.each( prototype, function( prop, value ) {\n\t\tif ( !$.isFunction( value ) ) {\n\t\t\tproxiedPrototype[ prop ] = value;\n\t\t\treturn;\n\t\t}\n\t\tproxiedPrototype[ prop ] = (function() {\n\t\t\tvar _super = function() {\n\t\t\t\t\treturn base.prototype[ prop ].apply( this, arguments );\n\t\t\t\t},\n\t\t\t\t_superApply = function( args ) {\n\t\t\t\t\treturn base.prototype[ prop ].apply( this, args );\n\t\t\t\t};\n\t\t\treturn function() {\n\t\t\t\tvar __super = this._super,\n\t\t\t\t\t__superApply = this._superApply,\n\t\t\t\t\treturnValue;\n\n\t\t\t\tthis._super = _super;\n\t\t\t\tthis._superApply = _superApply;\n\n\t\t\t\treturnValue = value.apply( this, arguments );\n\n\t\t\t\tthis._super = __super;\n\t\t\t\tthis._superApply = __superApply;\n\n\t\t\t\treturn returnValue;\n\t\t\t};\n\t\t})();\n\t});\n\tconstructor.prototype = $.widget.extend( basePrototype, {\n\t\t// TODO: remove support for widgetEventPrefix\n\t\t// always use the name + a colon as the prefix, e.g., draggable:start\n\t\t// don't prefix for widgets that aren't DOM-based\n\t\twidgetEventPrefix: existingConstructor ? basePrototype.widgetEventPrefix : name\n\t}, proxiedPrototype, {\n\t\tconstructor: constructor,\n\t\tnamespace: namespace,\n\t\twidgetName: name,\n\t\twidgetFullName: fullName\n\t});\n\n\t// If this widget is being redefined then we need to find all widgets that\n\t// are inheriting from it and redefine all of them so that they inherit from\n\t// the new version of this widget. We're essentially trying to replace one\n\t// level in the prototype chain.\n\tif ( existingConstructor ) {\n\t\t$.each( existingConstructor._childConstructors, function( i, child ) {\n\t\t\tvar childPrototype = child.prototype;\n\n\t\t\t// redefine the child widget using the same prototype that was\n\t\t\t// originally used, but inherit from the new version of the base\n\t\t\t$.widget( childPrototype.namespace + \".\" + childPrototype.widgetName, constructor, child._proto );\n\t\t});\n\t\t// remove the list of existing child constructors from the old constructor\n\t\t// so the old child constructors can be garbage collected\n\t\tdelete existingConstructor._childConstructors;\n\t} else {\n\t\tbase._childConstructors.push( constructor );\n\t}\n\n\t$.widget.bridge( name, constructor );\n};\n\n$.widget.extend = function( target ) {\n\tvar input = slice.call( arguments, 1 ),\n\t\tinputIndex = 0,\n\t\tinputLength = input.length,\n\t\tkey,\n\t\tvalue;\n\tfor ( ; inputIndex < inputLength; inputIndex++ ) {\n\t\tfor ( key in input[ inputIndex ] ) {\n\t\t\tvalue = input[ inputIndex ][ key ];\n\t\t\tif ( input[ inputIndex ].hasOwnProperty( key ) && value !== undefined ) {\n\t\t\t\t// Clone objects\n\t\t\t\tif ( $.isPlainObject( value ) ) {\n\t\t\t\t\ttarget[ key ] = $.isPlainObject( target[ key ] ) ?\n\t\t\t\t\t\t$.widget.extend( {}, target[ key ], value ) :\n\t\t\t\t\t\t// Don't extend strings, arrays, etc. with objects\n\t\t\t\t\t\t$.widget.extend( {}, value );\n\t\t\t\t// Copy everything else by reference\n\t\t\t\t} else {\n\t\t\t\t\ttarget[ key ] = value;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn target;\n};\n\n$.widget.bridge = function( name, object ) {\n\tvar fullName = object.prototype.widgetFullName || name;\n\t$.fn[ name ] = function( options ) {\n\t\tvar isMethodCall = typeof options === \"string\",\n\t\t\targs = slice.call( arguments, 1 ),\n\t\t\treturnValue = this;\n\n\t\t// allow multiple hashes to be passed on init\n\t\toptions = !isMethodCall && args.length ?\n\t\t\t$.widget.extend.apply( null, [ options ].concat(args) ) :\n\t\t\toptions;\n\n\t\tif ( isMethodCall ) {\n\t\t\tthis.each(function() {\n\t\t\t\tvar methodValue,\n\t\t\t\t\tinstance = $.data( this, fullName );\n\t\t\t\tif ( !instance ) {\n\t\t\t\t\treturn $.error( \"cannot call methods on \" + name + \" prior to initialization; \" +\n\t\t\t\t\t\t\"attempted to call method '\" + options + \"'\" );\n\t\t\t\t}\n\t\t\t\tif ( !$.isFunction( instance[options] ) || options.charAt( 0 ) === \"_\" ) {\n\t\t\t\t\treturn $.error( \"no such method '\" + options + \"' for \" + name + \" widget instance\" );\n\t\t\t\t}\n\t\t\t\tmethodValue = instance[ options ].apply( instance, args );\n\t\t\t\tif ( methodValue !== instance && methodValue !== undefined ) {\n\t\t\t\t\treturnValue = methodValue && methodValue.jquery ?\n\t\t\t\t\t\treturnValue.pushStack( methodValue.get() ) :\n\t\t\t\t\t\tmethodValue;\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t});\n\t\t} else {\n\t\t\tthis.each(function() {\n\t\t\t\tvar instance = $.data( this, fullName );\n\t\t\t\tif ( instance ) {\n\t\t\t\t\tinstance.option( options || {} )._init();\n\t\t\t\t} else {\n\t\t\t\t\t$.data( this, fullName, new object( options, this ) );\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\n\t\treturn returnValue;\n\t};\n};\n\n$.Widget = function( /* options, element */ ) {};\n$.Widget._childConstructors = [];\n\n$.Widget.prototype = {\n\twidgetName: \"widget\",\n\twidgetEventPrefix: \"\",\n\tdefaultElement: \"<div>\",\n\toptions: {\n\t\tdisabled: false,\n\n\t\t// callbacks\n\t\tcreate: null\n\t},\n\t_createWidget: function( options, element ) {\n\t\telement = $( element || this.defaultElement || this )[ 0 ];\n\t\tthis.element = $( element );\n\t\tthis.uuid = uuid++;\n\t\tthis.eventNamespace = \".\" + this.widgetName + this.uuid;\n\t\tthis.options = $.widget.extend( {},\n\t\t\tthis.options,\n\t\t\tthis._getCreateOptions(),\n\t\t\toptions );\n\n\t\tthis.bindings = $();\n\t\tthis.hoverable = $();\n\t\tthis.focusable = $();\n\n\t\tif ( element !== this ) {\n\t\t\t$.data( element, this.widgetFullName, this );\n\t\t\tthis._on( true, this.element, {\n\t\t\t\tremove: function( event ) {\n\t\t\t\t\tif ( event.target === element ) {\n\t\t\t\t\t\tthis.destroy();\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t});\n\t\t\tthis.document = $( element.style ?\n\t\t\t\t// element within the document\n\t\t\t\telement.ownerDocument :\n\t\t\t\t// element is window or document\n\t\t\t\telement.document || element );\n\t\t\tthis.window = $( this.document[0].defaultView || this.document[0].parentWindow );\n\t\t}\n\n\t\tthis._create();\n\t\tthis._trigger( \"create\", null, this._getCreateEventData() );\n\t\tthis._init();\n\t},\n\t_getCreateOptions: $.noop,\n\t_getCreateEventData: $.noop,\n\t_create: $.noop,\n\t_init: $.noop,\n\n\tdestroy: function() {\n\t\tthis._destroy();\n\t\t// we can probably remove the unbind calls in 2.0\n\t\t// all event bindings should go through this._on()\n\t\tthis.element\n\t\t\t.unbind( this.eventNamespace )\n\t\t\t// 1.9 BC for #7810\n\t\t\t// TODO remove dual storage\n\t\t\t.removeData( this.widgetName )\n\t\t\t.removeData( this.widgetFullName )\n\t\t\t// support: jquery <1.6.3\n\t\t\t// http://bugs.jquery.com/ticket/9413\n\t\t\t.removeData( $.camelCase( this.widgetFullName ) );\n\t\tthis.widget()\n\t\t\t.unbind( this.eventNamespace )\n\t\t\t.removeAttr( \"aria-disabled\" )\n\t\t\t.removeClass(\n\t\t\t\tthis.widgetFullName + \"-disabled \" +\n\t\t\t\t\"ui-state-disabled\" );\n\n\t\t// clean up events and states\n\t\tthis.bindings.unbind( this.eventNamespace );\n\t\tthis.hoverable.removeClass( \"ui-state-hover\" );\n\t\tthis.focusable.removeClass( \"ui-state-focus\" );\n\t},\n\t_destroy: $.noop,\n\n\twidget: function() {\n\t\treturn this.element;\n\t},\n\n\toption: function( key, value ) {\n\t\tvar options = key,\n\t\t\tparts,\n\t\t\tcurOption,\n\t\t\ti;\n\n\t\tif ( arguments.length === 0 ) {\n\t\t\t// don't return a reference to the internal hash\n\t\t\treturn $.widget.extend( {}, this.options );\n\t\t}\n\n\t\tif ( typeof key === \"string\" ) {\n\t\t\t// handle nested keys, e.g., \"foo.bar\" => { foo: { bar: ___ } }\n\t\t\toptions = {};\n\t\t\tparts = key.split( \".\" );\n\t\t\tkey = parts.shift();\n\t\t\tif ( parts.length ) {\n\t\t\t\tcurOption = options[ key ] = $.widget.extend( {}, this.options[ key ] );\n\t\t\t\tfor ( i = 0; i < parts.length - 1; i++ ) {\n\t\t\t\t\tcurOption[ parts[ i ] ] = curOption[ parts[ i ] ] || {};\n\t\t\t\t\tcurOption = curOption[ parts[ i ] ];\n\t\t\t\t}\n\t\t\t\tkey = parts.pop();\n\t\t\t\tif ( value === undefined ) {\n\t\t\t\t\treturn curOption[ key ] === undefined ? null : curOption[ key ];\n\t\t\t\t}\n\t\t\t\tcurOption[ key ] = value;\n\t\t\t} else {\n\t\t\t\tif ( value === undefined ) {\n\t\t\t\t\treturn this.options[ key ] === undefined ? null : this.options[ key ];\n\t\t\t\t}\n\t\t\t\toptions[ key ] = value;\n\t\t\t}\n\t\t}\n\n\t\tthis._setOptions( options );\n\n\t\treturn this;\n\t},\n\t_setOptions: function( options ) {\n\t\tvar key;\n\n\t\tfor ( key in options ) {\n\t\t\tthis._setOption( key, options[ key ] );\n\t\t}\n\n\t\treturn this;\n\t},\n\t_setOption: function( key, value ) {\n\t\tthis.options[ key ] = value;\n\n\t\tif ( key === \"disabled\" ) {\n\t\t\tthis.widget()\n\t\t\t\t.toggleClass( this.widgetFullName + \"-disabled ui-state-disabled\", !!value )\n\t\t\t\t.attr( \"aria-disabled\", value );\n\t\t\tthis.hoverable.removeClass( \"ui-state-hover\" );\n\t\t\tthis.focusable.removeClass( \"ui-state-focus\" );\n\t\t}\n\n\t\treturn this;\n\t},\n\n\tenable: function() {\n\t\treturn this._setOption( \"disabled\", false );\n\t},\n\tdisable: function() {\n\t\treturn this._setOption( \"disabled\", true );\n\t},\n\n\t_on: function( suppressDisabledCheck, element, handlers ) {\n\t\tvar delegateElement,\n\t\t\tinstance = this;\n\n\t\t// no suppressDisabledCheck flag, shuffle arguments\n\t\tif ( typeof suppressDisabledCheck !== \"boolean\" ) {\n\t\t\thandlers = element;\n\t\t\telement = suppressDisabledCheck;\n\t\t\tsuppressDisabledCheck = false;\n\t\t}\n\n\t\t// no element argument, shuffle and use this.element\n\t\tif ( !handlers ) {\n\t\t\thandlers = element;\n\t\t\telement = this.element;\n\t\t\tdelegateElement = this.widget();\n\t\t} else {\n\t\t\t// accept selectors, DOM elements\n\t\t\telement = delegateElement = $( element );\n\t\t\tthis.bindings = this.bindings.add( element );\n\t\t}\n\n\t\t$.each( handlers, function( event, handler ) {\n\t\t\tfunction handlerProxy() {\n\t\t\t\t// allow widgets to customize the disabled handling\n\t\t\t\t// - disabled as an array instead of boolean\n\t\t\t\t// - disabled class as method for disabling individual parts\n\t\t\t\tif ( !suppressDisabledCheck &&\n\t\t\t\t\t\t( instance.options.disabled === true ||\n\t\t\t\t\t\t\t$( this ).hasClass( \"ui-state-disabled\" ) ) ) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\treturn ( typeof handler === \"string\" ? instance[ handler ] : handler )\n\t\t\t\t\t.apply( instance, arguments );\n\t\t\t}\n\n\t\t\t// copy the guid so direct unbinding works\n\t\t\tif ( typeof handler !== \"string\" ) {\n\t\t\t\thandlerProxy.guid = handler.guid =\n\t\t\t\t\thandler.guid || handlerProxy.guid || $.guid++;\n\t\t\t}\n\n\t\t\tvar match = event.match( /^(\\w+)\\s*(.*)$/ ),\n\t\t\t\teventName = match[1] + instance.eventNamespace,\n\t\t\t\tselector = match[2];\n\t\t\tif ( selector ) {\n\t\t\t\tdelegateElement.delegate( selector, eventName, handlerProxy );\n\t\t\t} else {\n\t\t\t\telement.bind( eventName, handlerProxy );\n\t\t\t}\n\t\t});\n\t},\n\n\t_off: function( element, eventName ) {\n\t\teventName = (eventName || \"\").split( \" \" ).join( this.eventNamespace + \" \" ) + this.eventNamespace;\n\t\telement.unbind( eventName ).undelegate( eventName );\n\t},\n\n\t_delay: function( handler, delay ) {\n\t\tfunction handlerProxy() {\n\t\t\treturn ( typeof handler === \"string\" ? instance[ handler ] : handler )\n\t\t\t\t.apply( instance, arguments );\n\t\t}\n\t\tvar instance = this;\n\t\treturn setTimeout( handlerProxy, delay || 0 );\n\t},\n\n\t_hoverable: function( element ) {\n\t\tthis.hoverable = this.hoverable.add( element );\n\t\tthis._on( element, {\n\t\t\tmouseenter: function( event ) {\n\t\t\t\t$( event.currentTarget ).addClass( \"ui-state-hover\" );\n\t\t\t},\n\t\t\tmouseleave: function( event ) {\n\t\t\t\t$( event.currentTarget ).removeClass( \"ui-state-hover\" );\n\t\t\t}\n\t\t});\n\t},\n\n\t_focusable: function( element ) {\n\t\tthis.focusable = this.focusable.add( element );\n\t\tthis._on( element, {\n\t\t\tfocusin: function( event ) {\n\t\t\t\t$( event.currentTarget ).addClass( \"ui-state-focus\" );\n\t\t\t},\n\t\t\tfocusout: function( event ) {\n\t\t\t\t$( event.currentTarget ).removeClass( \"ui-state-focus\" );\n\t\t\t}\n\t\t});\n\t},\n\n\t_trigger: function( type, event, data ) {\n\t\tvar prop, orig,\n\t\t\tcallback = this.options[ type ];\n\n\t\tdata = data || {};\n\t\tevent = $.Event( event );\n\t\tevent.type = ( type === this.widgetEventPrefix ?\n\t\t\ttype :\n\t\t\tthis.widgetEventPrefix + type ).toLowerCase();\n\t\t// the original event may come from any element\n\t\t// so we need to reset the target on the new event\n\t\tevent.target = this.element[ 0 ];\n\n\t\t// copy original event properties over to the new event\n\t\torig = event.originalEvent;\n\t\tif ( orig ) {\n\t\t\tfor ( prop in orig ) {\n\t\t\t\tif ( !( prop in event ) ) {\n\t\t\t\t\tevent[ prop ] = orig[ prop ];\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tthis.element.trigger( event, data );\n\t\treturn !( $.isFunction( callback ) &&\n\t\t\tcallback.apply( this.element[0], [ event ].concat( data ) ) === false ||\n\t\t\tevent.isDefaultPrevented() );\n\t}\n};\n\n$.each( { show: \"fadeIn\", hide: \"fadeOut\" }, function( method, defaultEffect ) {\n\t$.Widget.prototype[ \"_\" + method ] = function( element, options, callback ) {\n\t\tif ( typeof options === \"string\" ) {\n\t\t\toptions = { effect: options };\n\t\t}\n\t\tvar hasOptions,\n\t\t\teffectName = !options ?\n\t\t\t\tmethod :\n\t\t\t\toptions === true || typeof options === \"number\" ?\n\t\t\t\t\tdefaultEffect :\n\t\t\t\t\toptions.effect || defaultEffect;\n\t\toptions = options || {};\n\t\tif ( typeof options === \"number\" ) {\n\t\t\toptions = { duration: options };\n\t\t}\n\t\thasOptions = !$.isEmptyObject( options );\n\t\toptions.complete = callback;\n\t\tif ( options.delay ) {\n\t\t\telement.delay( options.delay );\n\t\t}\n\t\tif ( hasOptions && $.effects && $.effects.effect[ effectName ] ) {\n\t\t\telement[ method ]( options );\n\t\t} else if ( effectName !== method && element[ effectName ] ) {\n\t\t\telement[ effectName ]( options.duration, options.easing, callback );\n\t\t} else {\n\t\t\telement.queue(function( next ) {\n\t\t\t\t$( this )[ method ]();\n\t\t\t\tif ( callback ) {\n\t\t\t\t\tcallback.call( element[ 0 ] );\n\t\t\t\t}\n\t\t\t\tnext();\n\t\t\t});\n\t\t}\n\t};\n});\n\n}));\n"
  },
  {
    "path": "samples/photo_album/src/main/webapp/assets/stylesheets/application.css",
    "content": "body { font-family: Helvetica, Arial, sans-serif; color: #333; margin: 10px; width: 960px }\n#posterframe { position: absolute; right: 10px; top: 10px; }\nh1 { color: #0e2953; font-size: 18px; }\nh2 { color: #666; font-size: 16px; }\np { font-size: 14px; line-height: 18px; }\n#logo { height: 51px; width: 241px; }\na { color: #0b63b6 }\n\n.actions { margin: 20px 0; }\n.upload_link { color: #000; border: 1px solid #aaa; background-color: #e0e0e0;\n    font-size: 18px; padding: 5px 10px; width: 250px; margin: 10px 0 20px 0;\n    font-weight: bold; text-align: center; text-decoration: none; margin: 5px; }\n\n.photo { margin: 10px; padding: 10px; border-top: 2px solid #ccc; }\n.photo .thumbnail { margin-top: 10px; display: block; max-width: 200px; border: none; }\n.toggle_info { margin-top: 10px; font-weight: bold; color: #e62401; display: block; }\n.thumbnail_holder { height: 182px; margin-bottom: 5px; margin-right: 10px; }\n.info td, .uploaded_info td { font-size: 12px }\n.note { margin: 20px 0}\n\n.more_info, .show_more_info .less_info { display: none; }\n.show_more_info .more_info, .less_info { display: inline-block; }\n.inline { display: inline-block; }\ntd { vertical-align: top; padding-right: 5px; }\n\n#backend_upload, #direct_upload { padding: 20px 20px; margin: 20px 0;\n    border-top: 1px solid #ccc; border-bottom: 1px solid #ccc; }\n\n#backend_upload h1, #direct_upload h1 { margin: 0 0 15px 0; }\n\n.back_link { font-weight: bold; display: block; font-size: 16px; margin: 10px 0; }\n\nform { border: 1px solid #ddd; margin: 15px 0; padding: 15px 0; border-radius: 4px; }\nform .form_line { margin-bottom: 20px; }\nform .form_controls { margin-left: 180px; }\nform label { float: left; width: 160px; padding-top: 3px; text-align: right; }\nform .error { color: #c55; margin: 0 10px; }\n\n#direct_upload { border: 4px dashed #ccc; }\n\n.upload_details { font-size: 12px; margin: 20px; border-top: 1px solid #ccc; word-wrap: break-word; }\n\n.upload_button_holder {\n    position: relative;\n    display: inline-block;\n    overflow: hidden;\n}\n\n.upload_button_holder .upload_button {\n    display: block;\n    position: relative;\n    font-weight: bold;\n    font-size: 14px;\n    background-color: rgb(15, 97, 172);\n    color: #fff;\n    padding: 5px 0;\n    border: 1px solid #000;\n    border-radius: 4px;\n    width: 100px;\n    height: 18px;\n    text-decoration: none;\n    text-align: center;\n    cursor: pointer;\n}\n\n.upload_button_holder:hover .upload_button {\n    background-color: rgb(17, 133, 240);\n}\n\n.upload_button_holder .cloudinary-fileupload {\n    opacity: 0;\n    filter: alpha(opacity=0);\n    cursor: pointer;\n    position: absolute;\n    top: 0;\n    left: 0;\n    width: 100%;\n    height: 100%;\n    margin: 0;\n    padding: 0;\n    border: none;\n}\n"
  },
  {
    "path": "samples/photo_album_gae/META-INF/persistence.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<persistence xmlns=\"http://java.sun.com/xml/ns/persistence\" version=\"2.0\">\n\n</persistence>"
  },
  {
    "path": "samples/photo_album_gae/README.md",
    "content": "Cloudinary Java/Spring MVC Sample Project on Google AppEngine\n=============================================================\n\nA simple web application that allows you to uploads photos, maintain a database with references to them, list them with their metadata, and display them using various cloud-based transformations.\n\n## Installation\n\nRun the following commands from your shell.\n\nClone the Cloudinary Java project: \n\n    git clone git://github.com/cloudinary/cloudinary_java.git      \n\nThis sample relies on a version of the Cloudinary client not yet published so we need to install it locally:\n\n    cd cloudinary_java\n    mvn install\n\nGo to the sample project folder:\n\n    cd samples/photo_album_gae\n\n## Configuration\n\nNext you need to pass your Cloudinary account's Cloud Name, API Key, and API Secret. This sample application assumes these values exists in the form\nof a `CLOUDINARY_URL`. The `CLOUDINARY_URL` value is available in the [dashboard of your Cloudinary account](https://cloudinary.com/console). \nIf you don't have a Cloudinary account yet, [click here](https://cloudinary.com/users/register/free) to create one for free.\n\n    cp src/main/webapp/WEB-INF/appengine-web.xml.sample src/main/webapp/WEB-INF/appengine-web.xml\n\nEdit the new `appengine-web.xml` file and set `CLOUDINARY_URL` to the value matching your account.\n*Note*: you can also change the application ID to match a remote project ID you might have started.\n\n## Running\n\nRunning the application locally first clean and compile to make sure everything is working as it should:\n\n    mvn clean compile\n\nThen start the application:  \n\n    mvn appengine:devserver_start\n\nand point your browser to [http://localhost:8080/](http://localhost:8080/).\n\nThis sample configures the Cloudinary client with `GAEConnectionManager` from [here](http://esxx.blogspot.co.il/2009/06/using-apaches-httpclient-on-google-app.html).\nYou can use any other connection manager or leave the default if you have the sockets enabled for your application. In the development environment it should work with either.\nAlso, the way the `GAEConnectionManager` is instantiated and managed in this sample is not pretty. You should change it when building a real application."
  },
  {
    "path": "samples/photo_album_gae/nbactions.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<actions>\n    <action>\n        <actionName>CUSTOM-appengine:devserver</actionName>\n        <displayName>appengine:devserver</displayName>\n        <goals>\n            <goal>appengine:devserver</goal>\n        </goals>\n    </action>\n    <action>\n        <actionName>CUSTOM-appengine:update</actionName>\n        <displayName>appengine:update</displayName>\n        <goals>\n            <goal>appengine:update</goal>\n        </goals>\n    </action>\n    <action>\n        <actionName>CUSTOM-appengine:rollback</actionName>\n        <displayName>appengine:rollback</displayName>\n        <goals>\n            <goal>appengine:rollback</goal>\n        </goals>\n    </action>\n    <action>\n        <actionName>CUSTOM-appengine:update_cron</actionName>\n        <displayName>appengine:update_cron</displayName>\n        <goals>\n            <goal>appengine:update_cron</goal>\n        </goals>\n    </action>\n    <action>\n        <actionName>CUSTOM-appengine:update_dos</actionName>\n        <displayName>appengine:update_dos</displayName>\n        <goals>\n            <goal>appengine:update_dos</goal>\n        </goals>\n    </action>\n    <action>\n        <actionName>CUSTOM-appengine:update_indexes</actionName>\n        <displayName>appengine:update_indexes</displayName>\n        <goals>\n            <goal>appengine:update_indexes</goal>\n        </goals>\n    </action>\n    <action>\n        <actionName>CUSTOM-appengine:update_queues</actionName>\n        <displayName>appengine:update_queues</displayName>\n        <goals>\n            <goal>appengine:update_queues</goal>\n        </goals>\n    </action>\n</actions>\n"
  },
  {
    "path": "samples/photo_album_gae/pom.xml",
    "content": "<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n    <groupId>com.cloudinary</groupId>\n    <artifactId>photo_album_gae</artifactId>\n    <packaging>war</packaging>\n    <version>1.0-SNAPSHOT</version>\n    <name>photo_album_gae</name>\n\n    <properties>\n        <spring.version>5.3.19</spring.version>\n        <appengine.app.version>1</appengine.app.version>\n        <appengine.target.version>1.9.37</appengine.target.version>\n        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n    </properties>\n\n    <repositories>\n        <repository>\n            <id>JBoss Repository</id>\n            <url>https://repository.jboss.org/nexus/content/repositories/releases</url>\n            <name>JBoss Repository</name>\n        </repository>\n        <repository>\n            <id>gmultipart</id>\n            <url>http://gmultipart.googlecode.com/svn/repo/m2</url>\n        </repository>\n    </repositories>\n\n    <pluginRepositories>\n      <pluginRepository>\n          <id>google-staging</id>\n          <name>Google Staging</name>\n          <url>https://oss.sonatype.org/content/repositories/comgoogleappengine-1004/</url>\n      </pluginRepository>\n    </pluginRepositories>\n\n    <dependencies>\n        <dependency>\n            <groupId>com.cloudinary</groupId>\n            <artifactId>cloudinary-taglib</artifactId>\n            <version>1.4.1</version>\n        </dependency>\n        <dependency>\n            <groupId>com.cloudinary</groupId>\n            <artifactId>cloudinary-http5</artifactId>\n            <version>2.0.0</version>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework</groupId>\n            <artifactId>spring-core</artifactId>\n            <version>${spring.version}</version>\n        </dependency>\n\n        <dependency>\n            <groupId>org.springframework</groupId>\n            <artifactId>spring-web</artifactId>\n            <version>${spring.version}</version>\n        </dependency>\n\n        <dependency>\n            <groupId>javax.servlet</groupId>\n            <artifactId>servlet-api</artifactId>\n            <version>2.5</version>\n        </dependency>\n\n        <dependency>\n            <groupId>javax.servlet.jsp</groupId>\n            <artifactId>jsp-api</artifactId>\n            <version>2.1</version>\n            <scope>provided</scope>\n        </dependency>\n\n        <dependency>\n            <groupId>org.springframework</groupId>\n            <artifactId>spring-webmvc</artifactId>\n            <version>${spring.version}</version>\n        </dependency>\n\n        <dependency>\n            <groupId>org.springframework</groupId>\n            <artifactId>spring-test</artifactId>\n            <version>${spring.version}</version>\n            <scope>test</scope>\n        </dependency>\n\n        <dependency>\n            <groupId>junit</groupId>\n            <artifactId>junit</artifactId>\n            <version>4.12</version>\n            <scope>test</scope>\n        </dependency>\n\n        <dependency>\n            <groupId>jstl</groupId>\n            <artifactId>jstl</artifactId>\n            <version>1.2</version>\n        </dependency>\n\n        <dependency>\n            <groupId>org.springframework.data</groupId>\n            <artifactId>spring-data-jpa</artifactId>\n            <version>1.11.20.RELEASE</version>\n        </dependency>\n\n        <dependency>\n            <groupId>com.google.appengine</groupId>\n            <artifactId>appengine-api-1.0-sdk</artifactId>\n            <version>${appengine.target.version}</version>\n        </dependency>\n\n        <dependency>\n            <groupId>commons-fileupload</groupId>\n            <artifactId>commons-fileupload</artifactId>\n            <version>1.3.3</version>\n        </dependency>\n\n        <dependency>\n            <groupId>javax.validation</groupId>\n            <artifactId>validation-api</artifactId>\n            <version>1.1.0.Final</version>\n        </dependency>\n\n        <dependency>\n            <groupId>org.mockito</groupId>\n            <artifactId>mockito-all</artifactId>\n            <version>1.9.0</version>\n            <scope>test</scope>\n        </dependency>\n\n        <dependency>\n            <groupId>com.google.appengine</groupId>\n            <artifactId>appengine-testing</artifactId>\n            <version>${appengine.target.version}</version>\n            <scope>test</scope>\n        </dependency>\n        <dependency>\n            <groupId>com.google.appengine</groupId>\n            <artifactId>appengine-api-stubs</artifactId>\n            <version>${appengine.target.version}</version>\n            <scope>test</scope>\n        </dependency>\n\n        <dependency>\n            <groupId>org.hibernate</groupId>\n            <artifactId>hibernate-validator-annotation-processor</artifactId>\n            <version>4.1.0.Final</version>\n        </dependency>\n\n        <dependency>\n            <groupId>gmultipart</groupId>\n            <artifactId>gmultipart</artifactId>\n            <version>0.4</version>\n        </dependency>\n    </dependencies>\n\n    <build>\n        <finalName>photo_album</finalName>\n        <plugins>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <version>2.5.1</version>\n                <artifactId>maven-compiler-plugin</artifactId>\n                <configuration>\n                    <source>1.7</source>\n                    <target>1.7</target>\n                </configuration>\n            </plugin>\n\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-war-plugin</artifactId>\n                <version>2.3</version>\n                <configuration>\n                    <archiveClasses>true</archiveClasses>\n                    <webResources>\n                        <!-- in order to interpolate version from pom into appengine-web.xml -->\n                        <resource>\n                            <directory>${basedir}/src/main/webapp/WEB-INF</directory>\n                            <filtering>true</filtering>\n                            <targetPath>WEB-INF</targetPath>\n                        </resource>\n                    </webResources>\n                </configuration>\n            </plugin>\n\n            <plugin>\n                <groupId>com.google.appengine</groupId>\n                <artifactId>appengine-maven-plugin</artifactId>\n                <version>${appengine.target.version}</version>\n            </plugin>\n        </plugins>\n    </build>\n</project>\n"
  },
  {
    "path": "samples/photo_album_gae/src/main/java/cloudinary/controllers/PhotoController.java",
    "content": "package cloudinary.controllers;\n\nimport cloudinary.lib.PhotoUploadValidator;\nimport cloudinary.models.PhotoUpload;\nimport com.cloudinary.utils.ObjectUtils;\nimport com.cloudinary.Singleton;\nimport org.springframework.stereotype.Controller;\nimport org.springframework.ui.ModelMap;\nimport org.springframework.validation.BindingResult;\nimport org.springframework.web.bind.annotation.*;\n\nimport com.google.appengine.api.datastore.DatastoreService;\nimport com.google.appengine.api.datastore.DatastoreServiceFactory;\nimport com.google.appengine.api.datastore.Entity;\nimport com.google.appengine.api.datastore.Key;\nimport com.google.appengine.api.datastore.KeyFactory;\nimport com.google.appengine.api.datastore.Query;\nimport com.google.appengine.api.datastore.FetchOptions;\n\n\nimport java.io.IOException;\nimport java.util.Map;\nimport java.util.List;\n\nimport org.esxx.js.protocol.GAEConnectionManager;\n\n@Controller\n@RequestMapping(\"/\")\npublic class PhotoController {\n\n\tprivate final static GAEConnectionManager connectionManager = new GAEConnectionManager();\n    @RequestMapping(value = \"/\", method = RequestMethod.GET)\n    public String listPhotos(ModelMap model) {\n    \tDatastoreService datastore = DatastoreServiceFactory.getDatastoreService();\n    \tKey photoKey = KeyFactory.createKey(\"photos\", \"album\");\n    \tList<Entity> photoEntities = datastore.prepare(new Query(\"photo\", photoKey)).asList(FetchOptions.Builder.withDefaults());\n    \tList<PhotoUpload> photos = new java.util.ArrayList<PhotoUpload>();\n\t\tfor(int i = 0, n = photoEntities.size(); i < n; i++) {\t\n    \t\tphotos.add(new PhotoUpload(photoEntities.get(i)));\n    \t}\n        model.addAttribute(\"photos\", photos);\n        return \"photos\";\n    }\n\n    @RequestMapping(value = \"/upload\", method = RequestMethod.POST)\n    public String uploadPhoto(@ModelAttribute PhotoUpload photoUpload, BindingResult result, ModelMap model) throws IOException {\n        PhotoUploadValidator validator = new PhotoUploadValidator();\n        validator.validate(photoUpload, result);\n\n        Map uploadResult = null;\n        if (photoUpload.getFile() != null && !photoUpload.getFile().isEmpty()) {            \n            Singleton.getCloudinary().config.properties.put(\"connectionManager\", connectionManager);\n            uploadResult = Singleton.getCloudinary().uploader().upload(photoUpload.getFile().getBytes(),\n                    ObjectUtils.asMap(\"resource_type\", \"auto\"));\n            \n            photoUpload.setPublicId((String) uploadResult.get(\"public_id\"));\n            photoUpload.setVersion(((Integer) uploadResult.get(\"version\")).longValue());\n            photoUpload.setSignature((String) uploadResult.get(\"signature\"));\n            photoUpload.setFormat((String) uploadResult.get(\"format\"));\n            photoUpload.setResourceType((String) uploadResult.get(\"resource_type\"));\n        }\n\n        if (result.hasErrors()){\n            model.addAttribute(\"photoUpload\", photoUpload);\n            return \"upload_form\";\n        } else {\n        \tKey photoKey = KeyFactory.createKey(\"photos\", \"album\");\n        \tEntity photo = new Entity(\"photo\", photoKey);\n        \tphotoUpload.toEntity(photo);\n            model.addAttribute(\"upload\", uploadResult);\n            DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();\n            datastore.put(photo);\n            model.addAttribute(\"photo\", photoUpload);\n            return \"upload\";\n        }\n    }\n\n    @RequestMapping(value = \"/upload_form\", method = RequestMethod.GET)\n    public String uploadPhotoForm(ModelMap model) {\n        model.addAttribute(\"photo\", new PhotoUpload());\n        return \"upload_form\";\n    }\n\n    @RequestMapping(value = \"/direct_upload_form\", method = RequestMethod.GET)\n    public String directUploadPhotoForm(ModelMap model) {\n        model.addAttribute(\"photo\", new PhotoUpload());\n        return \"direct_upload_form\";\n    }\n}\n"
  },
  {
    "path": "samples/photo_album_gae/src/main/java/cloudinary/lib/PhotoUploadValidator.java",
    "content": "package cloudinary.lib;\n\nimport cloudinary.models.PhotoUpload;\nimport org.springframework.validation.Errors;\nimport org.springframework.validation.ValidationUtils;\nimport org.springframework.validation.Validator;\n\npublic class PhotoUploadValidator implements Validator {\n    public boolean supports(Class clazz) {\n        return PhotoUpload.class.equals(clazz);\n    }\n\n    public void validate(Object obj, Errors e) {\n        ValidationUtils.rejectIfEmpty(e, \"title\", \"title.empty\");\n        PhotoUpload pu = (PhotoUpload) obj;\n        if (pu.getFile() == null || pu.getFile().isEmpty()) {\n            if (!pu.validSignature()) {\n                e.rejectValue(\"signature\", \"signature.mismatch\");\n            }\n        }\n    }\n\n}\n"
  },
  {
    "path": "samples/photo_album_gae/src/main/java/cloudinary/models/PhotoUpload.java",
    "content": "package cloudinary.models;\n\nimport com.cloudinary.Singleton;\nimport com.cloudinary.StoredFile;\nimport com.cloudinary.Transformation;\nimport org.gmr.web.multipart.GMultipartFile;\nimport com.google.appengine.api.datastore.Entity;\n\npublic class PhotoUpload extends StoredFile {\n    private String title;\n\n    private GMultipartFile file;\n    public PhotoUpload() {\n    \tsuper();\n    }\n    \n    public PhotoUpload(Entity entity) {\n    \tsuper();\n    \tthis.publicId = entity.getProperty(\"public_id\").toString();\n    \tthis.format = entity.getProperty(\"format\").toString();\n    \tthis.version = (Long) entity.getProperty(\"version\");\n    \tthis.type = entity.getProperty(\"type\").toString();\n    \tthis.resourceType = entity.getProperty(\"resource_type\").toString();\n    \tthis.title = entity.getProperty(\"title\").toString();\n    }\n    \n    public String getUrl() {\n        if (version != null && format != null && publicId != null) {\n            return Singleton.getCloudinary().url()\n                    .resourceType(resourceType)\n                    .type(type)\n                    .format(format)\n                    .version(version)\n                    .generate(publicId);\n        } else return null;\n    }\n\n    public String getThumbnailUrl() {\n        if (version != null && format != null && publicId != null) {\n            return Singleton.getCloudinary().url().format(format)\n                    .resourceType(resourceType)\n                    .type(type)\n                    .version(version).transformation(new Transformation().width(150).height(150).crop(\"fit\"))\n                    .generate(publicId);\n        } else return null;\n    }\n\n    public String getComputedSignature() {\n        return getComputedSignature(Singleton.getCloudinary());\n    }\n\n    public boolean validSignature() {\n        return getComputedSignature().equals(signature);\n    }\n\n    public String getTitle() {\n        return title;\n    }\n\n    public void setTitle(String title) {\n        this.title = title;\n    }\n\n    public GMultipartFile getFile() {\n        return file;\n    }\n\n    public void setFile(GMultipartFile file) {\n        this.file = file;\n    }\n    \n    public void toEntity(Entity photo) {\n    \tphoto.setProperty(\"title\", getTitle());\n        photo.setProperty(\"version\", getVersion());\n        photo.setProperty(\"public_id\", getPublicId());\n        photo.setProperty(\"format\", getFormat());\n        photo.setProperty(\"url\", getUrl());\n        photo.setProperty(\"type\", getType());\n        photo.setProperty(\"resource_type\", getResourceType());\n    }\n}\n"
  },
  {
    "path": "samples/photo_album_gae/src/main/java/org/esxx/js/protocol/GAEClientConnection.java",
    "content": "/*\n     ESXX - The friendly ECMAscript/XML Application Server\n     Copyright (C) 2007-2010 Martin Blom <martin@blom.org>\n\n     This program is free software: you can redistribute it and/or\n     modify it under the terms of the GNU Lesser General Public License\n     as published by the Free Software Foundation, either version 3\n     of the License, or (at your option) any later version.\n\n     This program is distributed in the hope that it will be useful,\n     but WITHOUT ANY WARRANTY; without even the implied warranty of\n     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n     GNU Lesser General Public License for more details.\n\n     You should have received a copy of the GNU Lesser General Public License\n     along with this program.  If not, see <http://www.gnu.org/licenses/>.\n\n\n     PLEASE NOTE THAT THIS FILE'S LICENSE IS DIFFERENT FROM THE REST OF ESXX!\n*/\n\npackage org.esxx.js.protocol;\n\nimport java.io.*;\nimport java.net.*;\nimport java.util.concurrent.TimeUnit;\nimport org.apache.http.*;\nimport org.apache.http.conn.*;\nimport org.apache.http.conn.routing.HttpRoute;\nimport org.apache.http.entity.ByteArrayEntity;\nimport org.apache.http.message.BasicHttpResponse;\nimport org.apache.http.params.*;\nimport org.apache.http.protocol.*;\n\nimport com.google.appengine.api.urlfetch.*;\n\nclass GAEClientConnection\n  implements ManagedClientConnection {\n\n  public GAEClientConnection(ClientConnectionManager cm, HttpRoute route, Object state) {\n    this.connManager = cm;\n    this.route = route;\n    this.state = state;\n    this.closed = true;\n  }\n\n  // From interface ManagedClientConnection\n\n  @Override public boolean isSecure() {\n    return route.isSecure();\n  }\n\n  @Override public HttpRoute getRoute() {\n    return route;\n  }\n\n  @Override public javax.net.ssl.SSLSession getSSLSession() {\n    return null;\n  }\n\n  @Override public void open(HttpRoute route, HttpContext context, HttpParams params)\n    throws IOException {\n    close();\n    this.route = route;\n//     System.err.println(\">>>>\");\n  }\n\n  @Override public void tunnelTarget(boolean secure, HttpParams params)\n    throws IOException {\n    throw new IOException(\"tunnelTarget() not supported\");\n  }\n\n  @Override public void tunnelProxy(HttpHost next, boolean secure, HttpParams params)\n    throws IOException {\n    throw new IOException(\"tunnelProxy() not supported\");\n  }\n\n  @Override public void layerProtocol(HttpContext context, HttpParams params)\n    throws IOException {\n    throw new IOException(\"layerProtocol() not supported\");\n  }\n\n  @Override public void markReusable() {\n    reusable = true;\n  }\n\n  @Override public void unmarkReusable() {\n    reusable = false;\n  }\n\n  @Override public boolean isMarkedReusable() {\n    return reusable;\n  }\n\n  @Override public void setState(Object state) {\n    this.state = state;\n  }\n\n  @Override public Object getState() {\n    return state;\n  }\n\n  @Override public void setIdleDuration(long duration, TimeUnit unit) {\n    // Do nothing\n  }\n\n\n  // From interface HttpClientConnection\n\n  @Override public boolean isResponseAvailable(int timeout)\n    throws IOException {\n    return response != null;\n  }\n\n\n  @Override public void sendRequestHeader(HttpRequest request)\n    throws HttpException, IOException {\n    try {\n      HttpHost host = route.getTargetHost();\n\n      URI uri = new URI(host.getSchemeName()\n\t\t\t+ \"://\"\n\t\t\t+ host.getHostName()\n\t\t\t+ ((host.getPort() == -1) ? \"\" : (\":\" + host.getPort()))\n\t\t\t+ request.getRequestLine().getUri());\n\n      this.request = new HTTPRequest(uri.toURL(),\n\t\t\t\t     HTTPMethod.valueOf(request.getRequestLine().getMethod()),\n\t\t\t\t     FetchOptions.Builder.disallowTruncate().doNotFollowRedirects().setDeadline(60.0));\n    }\n    catch (URISyntaxException ex) {\n      throw new IOException(\"Malformed request URI: \" + ex.getMessage(), ex);\n    }\n    catch (IllegalArgumentException ex) {\n      throw new IOException(\"Unsupported HTTP method: \" + ex.getMessage(), ex);\n    }\n\n//     System.err.println(\"SEND: \" + this.request.getMethod() + \" \" + this.request.getURL());\n\n    for (Header h : request.getAllHeaders()) {\n//       System.err.println(\"SEND: \" + h.getName() + \": \" + h.getValue());\n      this.request.addHeader(new HTTPHeader(h.getName(), h.getValue()));\n    }\n  }\n\n\n  @Override public void sendRequestEntity(HttpEntityEnclosingRequest request)\n    throws HttpException, IOException {\n    ByteArrayOutputStream baos = new ByteArrayOutputStream();\n    if (request.getEntity() != null) {\n      request.getEntity().writeTo(baos);\n    }\n    this.request.setPayload(baos.toByteArray());\n  }\n\n\n  @Override public HttpResponse receiveResponseHeader()\n    throws HttpException, IOException {\n    if (this.response == null) {\n      flush();\n    }\n\n    HttpResponse response = new BasicHttpResponse(new ProtocolVersion(\"HTTP\", 1, 1),\n\t\t\t\t\t\t  this.response.getResponseCode(),\n\t\t\t\t\t\t  null);\n//     System.err.println(\"RECV: \" + response.getStatusLine());\n\n    for (HTTPHeader h : this.response.getHeaders()) {\n//       System.err.println(\"RECV: \" + h.getName() + \": \" + h.getValue());\n      response.addHeader(h.getName(), h.getValue());\n    }\n\n    return response;\n  }\n\n\n  @Override public void receiveResponseEntity(HttpResponse response)\n    throws HttpException, IOException {\n    if (this.response == null) {\n      throw new IOException(\"receiveResponseEntity() called on closed connection\");\n    }\n\n    ByteArrayEntity bae = new ByteArrayEntity(this.response.getContent());\n    bae.setContentType(response.getFirstHeader(\"Content-Type\"));\n    response.setEntity(bae);\n\n    response = null;\n  }\n\n  @Override public void flush()\n    throws IOException {\n    if (request != null) {\n      try {\n// \tSystem.err.println(\"----\");\n\tresponse = urlFS.fetch(request);\n\trequest = null;\n      }catch (IOException ex) {\n\tex.printStackTrace();\n\tthrow ex;\n      }\n    }\n    else {\n      response = null;\n    }\n  }\n\n\n  // From interface HttpConnection\n\n  @Override public void close()\n    throws IOException {\n    request  = null;\n    response = null;\n    closed   = true;\n//     System.err.println(\"<<<<\");\n  }\n\n  @Override public boolean isOpen() {\n    return request != null || response != null;\n  }\n\n  @Override public boolean isStale() {\n    return !isOpen() && !closed;\n  }\n\n  @Override public void setSocketTimeout(int timeout) {\n  }\n\n  @Override public int getSocketTimeout() {\n    return -1;\n  }\n\n  @Override public void shutdown()\n    throws IOException {\n    close();\n  }\n\n  @Override public HttpConnectionMetrics getMetrics() {\n    return null;\n  }\n\n\n  // From interface HttpInetConnection\n\n  @Override public InetAddress getLocalAddress() {\n    return null;\n  }\n\n  @Override public int getLocalPort() {\n    return 0;\n  }\n\n  @Override public InetAddress getRemoteAddress() {\n    return null;\n  }\n\n  @Override public int getRemotePort() {\n    HttpHost host = route.getTargetHost();\n    return connManager.getSchemeRegistry().getScheme(host).resolvePort(host.getPort());\n  }\n\n\n  // From interface ConnectionReleaseTrigger\n\n  @Override public void releaseConnection()\n    throws IOException {\n    connManager.releaseConnection(this, Long.MAX_VALUE, TimeUnit.MILLISECONDS);\n  }\n\n  @Override public void abortConnection()\n    throws IOException {\n    unmarkReusable();\n    shutdown();\n  }\n\n  private ClientConnectionManager connManager;\n  private HttpRoute route;\n  private Object state;\n  private boolean reusable;\n\n  private HTTPRequest request;\n  private HTTPResponse response;\n  private boolean closed;\n\n  private static URLFetchService urlFS = URLFetchServiceFactory.getURLFetchService();\n}\n"
  },
  {
    "path": "samples/photo_album_gae/src/main/java/org/esxx/js/protocol/GAEConnectionManager.java",
    "content": "/*\n     ESXX - The friendly ECMAscript/XML Application Server\n     Copyright (C) 2007-2010 Martin Blom <martin@blom.org>\n\n     This program is free software: you can redistribute it and/or\n     modify it under the terms of the GNU Lesser General Public License\n     as published by the Free Software Foundation, either version 3\n     of the License, or (at your option) any later version.\n\n     This program is distributed in the hope that it will be useful,\n     but WITHOUT ANY WARRANTY; without even the implied warranty of\n     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n     GNU Lesser General Public License for more details.\n\n     You should have received a copy of the GNU Lesser General Public License\n     along with this program.  If not, see <http://www.gnu.org/licenses/>.\n\n\n     PLEASE NOTE THAT THIS FILE'S LICENSE IS DIFFERENT FROM THE REST OF ESXX!\n*/\n\npackage org.esxx.js.protocol;\n\nimport java.net.*;\nimport java.util.concurrent.TimeUnit;\nimport org.apache.http.conn.*;\nimport org.apache.http.params.*;\nimport org.apache.http.conn.routing.HttpRoute;\nimport org.apache.http.conn.scheme.*;\n\npublic class GAEConnectionManager\n  implements ClientConnectionManager {\n  \n  public GAEConnectionManager() {\n    SocketFactory no_socket_factory = new SocketFactory() {\n\tpublic Socket connectSocket(Socket sock, String host, int port, \n\t\t\t\t    InetAddress localAddress, int localPort, \n\t\t\t\t    HttpParams params) {\n\t  return null;\n\t}\n\n\tpublic Socket createSocket() {\n\t  return null;\n\t}\n\n\tpublic boolean isSecure(Socket s) {\n\t  return false;\n\t}\n      };\n\n    schemeRegistry = new SchemeRegistry();\n    schemeRegistry.register(new Scheme(\"http\",  no_socket_factory, 80));\n    schemeRegistry.register(new Scheme(\"https\", no_socket_factory, 443));\n  }\n\n\n  @Override public SchemeRegistry getSchemeRegistry() {\n    return schemeRegistry;\n  }\n\n  @Override public ClientConnectionRequest requestConnection(final HttpRoute route, \n\t\t\t\t\t\t\t     final Object state) {\n    return new ClientConnectionRequest() {\n      public void abortRequest() {\n\t// Nothing to do\n      }\n\n      public ManagedClientConnection getConnection(long timeout, TimeUnit tunit) {\n\treturn GAEConnectionManager.this.getConnection(route, state);\n      }\n    };\n  }\n\n  @Override public void releaseConnection(ManagedClientConnection conn, \n\t\t\t\t\t  long validDuration, TimeUnit timeUnit) {\n  }\n\n  @Override public void closeIdleConnections(long idletime, TimeUnit tunit) {\n  }\n\n  @Override public void closeExpiredConnections() {\n  }\n\n  @Override public void shutdown() {\n  }\n\n  private ManagedClientConnection getConnection(HttpRoute route, Object state) {\n    return new GAEClientConnection(this, route, state);\n  }\n\n  private SchemeRegistry schemeRegistry;\n}\n"
  },
  {
    "path": "samples/photo_album_gae/src/main/webapp/WEB-INF/appengine-web.xml.sample",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<appengine-web-app xmlns=\"http://appengine.google.com/ns/1.0\">\n    <application>{Your Application ID}</application>\n    <version>1</version>\n    <threadsafe>true</threadsafe>\n    \n    <static-files>\n      <include path=\"/assets/**\" >\n      </include>\n    </static-files>\n\n    <system-properties>\n        <property name=\"java.util.logging.config.file\" value=\"WEB-INF/logging.properties\"/>\n        <property name=\"CLOUDINARY_URL\" value=\"{Your Cloudinary URL}\" />\n    </system-properties>\n    \n    <sessions-enabled>false</sessions-enabled>\n</appengine-web-app>\n"
  },
  {
    "path": "samples/photo_album_gae/src/main/webapp/WEB-INF/logging.properties",
    "content": "# A default java.util.logging configuration.\n# (All App Engine logging is through java.util.logging by default).\n#\n# To use this configuration, copy it into your application's WEB-INF\n# folder and add the following to your appengine-web.xml:\n# \n# <system-properties>\n#   <property name=\"java.util.logging.config.file\" value=\"WEB-INF/logging.properties\"/>\n# </system-properties>\n#\n\n# Set the default logging level for all loggers to WARNING\n.level = WARNING\n"
  },
  {
    "path": "samples/photo_album_gae/src/main/webapp/WEB-INF/messages.properties",
    "content": "title.empty = Title cannot be empty\nsignature.mismatch = Signature mismatch"
  },
  {
    "path": "samples/photo_album_gae/src/main/webapp/WEB-INF/mvc-dispatcher-servlet.xml",
    "content": "<beans xmlns=\"http://www.springframework.org/schema/beans\"\n       xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n       xmlns:context=\"http://www.springframework.org/schema/context\"\n       xmlns:mvc=\"http://www.springframework.org/schema/mvc\"\n       xsi:schemaLocation=\"http://www.springframework.org/schema/beans\n        http://www.springframework.org/schema/beans/spring-beans.xsd\n        http://www.springframework.org/schema/context\n        http://www.springframework.org/schema/context/spring-context.xsd\n        http://www.springframework.org/schema/mvc\n        http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd\">\n\n    <context:component-scan base-package=\"cloudinary\"/>\n\n    <bean class=\"org.springframework.web.servlet.view.InternalResourceViewResolver\">\n        <property name=\"prefix\" value=\"/WEB-INF/pages/\"/>\n        <property name=\"suffix\" value=\".jsp\"/>\n    </bean>\n    \n    <bean id=\"multipartResolver\" class=\"org.gmr.web.multipart.GMultipartResolver\">\n        <property name=\"maxUploadSize\" value=\"1048576\" />\n    </bean>\n\n    <bean id=\"messageSource\"\n          class=\"org.springframework.context.support.ReloadableResourceBundleMessageSource\">\n        <property name=\"basename\" value=\"/WEB-INF/messages\"/>\n    </bean>\n\n    <mvc:annotation-driven />\n\n    <mvc:default-servlet-handler/>\n\n    <mvc:resources mapping=\"/javascripts/*.js\" location=\"/assets/javascripts/\" />\n    <mvc:resources mapping=\"/javascripts/cloudinary/*.js\" location=\"/assets/javascripts/cloudinary/\" />\n    <mvc:resources mapping=\"/stylesheets/*.css\" location=\"/assets/stylesheets/\" />\n    <mvc:resources mapping=\"/cloudinary_cors.html\" location=\"/assets/\" />\n</beans>"
  },
  {
    "path": "samples/photo_album_gae/src/main/webapp/WEB-INF/pages/direct_upload_form.jsp",
    "content": "<!doctype html>\n<%@taglib uri=\"http://www.springframework.org/tags\" prefix=\"spring\" %>\n<%@taglib uri=\"http://www.springframework.org/tags/form\" prefix=\"form\" %>\n<%@taglib uri=\"http://java.sun.com/jsp/jstl/core\" prefix=\"c\" %>\n\n<%@include file=\"pre.jsp\"%>\n\n<div id=\"direct_upload\">\n    <h1>New Photo</h1>\n    <h2>Direct upload from the browser</h2>\n    <p>You can also drag and drop an image file into the dashed area.</p>\n    <form:form method=\"post\" action=\"upload\" commandName=\"photo\" enctype=\"multipart/form-data\">\n        <div class=\"form_line\">\n            <form:label path=\"title\">Title:</form:label>\n            <div class=\"form_controls\">\n                <form:input path=\"title\"/>\n                <form:errors path=\"title\" extraClasses=\"error\" />\n            </div>\n        </div>\n        <div class=\"form_line\">\n            <label>Image:</label>\n            <div class=\"form_controls\">\n                <div class=\"upload_button_holder\">\n                    <a href=\"#\" class=\"upload_button\">Upload</a>\n                    <cl:upload fieldName=\"preloadedFile\" transformation=\"w_1000,h_1000,c_limit\"\n                            eager=\"c_scale,w_150,h_150|c_fit,w_150,h_150\" extraClasses=\"extra\" exif=\"true\"\n                            imageMetadata=\"true\" colors=\"true\" faces=\"true\"/>\n                </div>\n                <span class=\"status\"></span>\n            </div>\n        </div>\n        <div class=\"form_line\">\n            <div class=\"form_controls\">\n                <div class=\"preview\"></div>\n            </div>\n        </div>\n        <div class=\"form_line\">\n            <div class=\"form_controls\">\n                <input type=\"submit\" value=\"Submit Photo\">\n            <form:errors path=\"signature\" extraClasses=\"error\" />\n            </div>\n        </div>\n    </form:form>\n</div>\n\n<a href=\"<c:url value=\"/\"/>\" class=\"back_link\">Back to list</a>\n\n<div id=\"info\"></div>\n\n<cl:jsinclude/>\n\n<!-- Configure Cloudinary jQuery plugin -->\n<cl:jsconfig/>\n\n<script type=\"text/javascript\">\n    $(document).ready(function() {\n        // Cloudinary jQuery integration library uses jQuery File Upload widget\n        // (see http://blueimp.github.io/jQuery-File-Upload/).\n        // Any file input field with cloudinary-fileupload class is automatically\n        // wrapped using the File Upload widget and configured for Cloudinary uploads.\n        // You can further customize the configuration using .fileupload method\n        // as we do below.\n        $(\".cloudinary-fileupload\")\n                .fileupload({\n                    // Uncomment the following lines to enable client side image resizing and valiation.\n                    // Make sure cloudinary/processing is included the js file\n                    //disableImageResize: false,\n                    //imageMaxWidth: 800,\n                    //imageMaxHeight: 600,\n                    //acceptFileTypes: /(\\.|\\/)(gif|jpe?g|png|bmp|ico)$/i,\n                    //maxFileSize: 20000000, // 20MB\n                    dropZone: \"#direct_upload\",\n                    start: function (e) {\n                        $(\".status\").text(\"Starting upload...\");\n                    },\n                    progress: function (e, data) {\n                        $(\".status\").text(\"Uploading... \" + Math.round((data.loaded * 100.0) / data.total) + \"%\");\n                    },\n                    fail: function (e, data) {\n                        $(\".status\").text(\"Upload failed\");\n                    }\n                })\n                .off(\"cloudinarydone\").on(\"cloudinarydone\", function (e, data) {\n                    $(\"#photo_bytes\").val(data.result.bytes);\n                    $(\".status\").text(\"\");\n                    if (data.result.resource_type == \"image\") {\n                        $(\".preview\").html(\n                                $.cloudinary.image(data.result.public_id, {\n                                    format: data.result.format, width: 50, height: 50, crop: \"fit\"\n                                })\n                        );\n                    }\n                    view_upload_details(data.result);\n                });\n    });\n\n    function view_upload_details(upload) {\n        // Build an html table out of the upload object\n        var rows = [];\n        $.each(upload, function(k,v){\n            rows.push(\n                    $(\"<tr>\")\n                            .append($(\"<td>\").text(k))\n                            .append($(\"<td>\").text(JSON.stringify(v))));\n        });\n        $(\"#info\").html(\n                $(\"<div class=\\\"upload_details\\\">\")\n                        .append(\"<h2>Upload metadata:</h2>\")\n                        .append($(\"<table>\").append(rows)));\n    }\n</script>\n\n<%@include file=\"post.jsp\"%>\n"
  },
  {
    "path": "samples/photo_album_gae/src/main/webapp/WEB-INF/pages/photos.jsp",
    "content": "<!doctype html>\n<%@include file=\"pre.jsp\"%>\n<%@taglib uri=\"http://www.springframework.org/tags\" prefix=\"spring\" %>\n<%@taglib uri=\"http://www.springframework.org/tags/form\" prefix=\"form\" %>\n<%@taglib uri=\"http://java.sun.com/jsp/jstl/core\" prefix=\"c\" %>\n\n<div id=\"posterframe\">\n<!-- This will render the fetched Facebook profile picture using Cloudinary according to the\nrequested transformations. This also shows how to chain transformations -->\n<cl:image src=\"officialchucknorrispage\" type=\"facebook\" format=\"png\" height=\"95\" width=\"95\" crop=\"thumb\" gravity=\"face\" effect=\"sepia\" radius=\"20\">\n    <jsp:attribute name=\"transformation\">\n        <cl:transformation angle=\"10\"/>\n    </jsp:attribute>\n</cl:image>\n</div>\n\n<h1>Welcome!</h1>\n\n<p>\n    This is the main demo page of the PhotoAlbum sample Spring MVC/Google AppEngine application of Cloudinary.<br />\n    Here you can see all images you have uploaded to this application and find some information on how\n    to implement your own Spring application storing, manipulating and serving your photos using Cloudinary!\n</p>\n\n<p>\n    All of the images you see here are transformed and served by Cloudinary.\n    For instance, the logo and the poster frame.\n    They are both generated in the cloud using the Cloudinary shortcut functions: fetch_image_tag and facebook_profile_image_tag.\n    These two pictures weren't even have to be uploaded to Cloudinary, they are retrieved by the service, transformed, cached and distributed through a CDN.\n</p>\n\n<h1>Your Photos</h1>\n\n<div class=\"actions\">\n    <a class=\"upload_link\" href=\"upload_form\">Add photo</a>\n    <a class=\"upload_link\" href=\"direct_upload_form\">Add photo (direct upload)</a>\n</div>\n\n<div class=\"photos\">\n    <c:if test=\"${empty photos}\">\n        <p>No photos were added yet.</p>\n    </c:if>\n\n    <c:if test=\"${!empty photos}\">\n        <c:forEach items=\"${photos}\" var=\"photo\">\n            <div class=\"photo\">\n                <h2>${photo.title}</h2>\n                <c:if test=\"${photo.isImage}\">\n                    <a href=\"<cl:url storedSrc=\"${photo}\" format=\"jpg\"/>\" target=\"_blank\">\n                        <cl:image storedSrc=\"${photo}\" extraClasses=\"thumbnail inline\" width=\"150\" height=\"150\" crop=\"fit\" quality=\"80\" format=\"jpg\"/>\n                    </a>\n\n                    <div class=\"less_info\">\n                        <a href=\"#\" class=\"toggle_info\">Show transformations</a>\n                    </div>\n\n                    <div class=\"more_info\">\n                        <a href=\"#\" class=\"toggle_info\">Hide transformations</a>\n                        <table class=\"thumbnails\">\n                            <td>\n                                <div class=\"thumbnail_holder\">\n                                    <cl:image storedSrc=\"${photo}\" extraClasses=\"thumbnail inline\" crop=\"fill\" height=\"150\" width=\"150\" radius=\"10\" format=\"jpg\"/>\n                                </div>\n                                <table class=\"info\">\n                                    <tr><td>crop</td><td>fill</td></tr>\n                                    <tr><td>width</td><td>150</td></tr>\n                                    <tr><td>height</td><td>150</td></tr>\n                                    <tr><td>radius</td><td>10</td></tr>\n                                </table>\n                                <br/>\n                            </td>\n                            <td>\n                                <div class=\"thumbnail_holder\">\n                                    <cl:image storedSrc=\"${photo}\" extraClasses=\"thumbnail inline\" crop=\"scale\" height=\"150\" width=\"150\" format=\"jpg\"/>\n                                </div>\n                                <table class=\"info\">\n                                    <tr><td>crop</td><td>scale</td></tr>\n                                    <tr><td>width</td><td>150</td></tr>\n                                    <tr><td>height</td><td>150</td></tr>\n                                </table>\n                                <br/>\n                            </td>\n                            <td>\n                                <div class=\"thumbnail_holder\">\n                                    <cl:image storedSrc=\"${photo}\" extraClasses=\"thumbnail inline\" crop=\"fit\" height=\"150\" width=\"150\" format=\"jpg\"/>\n                                </div>\n                                <table class=\"info\">\n                                    <tr><td>crop</td><td>fit</td></tr>\n                                    <tr><td>width</td><td>150</td></tr>\n                                    <tr><td>height</td><td>150</td></tr>\n                                </table>\n                                <br/>\n                            </td>\n                            <td>\n                                <div class=\"thumbnail_holder\">\n                                    <cl:image storedSrc=\"${photo}\" extraClasses=\"thumbnail inline\" crop=\"thumb\" gravity=\"face\" height=\"150\" width=\"150\" format=\"jpg\"/>\n                                </div>\n                                <table class=\"info\">\n                                    <tr><td>crop</td><td>thumb</td></tr>\n                                    <tr><td>gravity</td><td>face</td></tr>\n                                    <tr><td>width</td><td>150</td></tr>\n                                    <tr><td>height</td><td>150</td></tr>\n                                </table>\n                                <br/>\n                            </td>\n                            <td>\n                                <div class=\"thumbnail_holder\">\n                                    <cl:image storedSrc=\"${photo}\" extraClasses=\"thumbnail inline\" format=\"png\" angle=\"20\">\n                                        <jsp:attribute name=\"transformation\">\n                                            <cl:transformation crop=\"fill\" gravity=\"north\" height=\"150\" width=\"150\" effect=\"sepia\"/>\n                                        </jsp:attribute>\n                                    </cl:image>\n                                </div>\n                                <table class=\"info\">\n                                    <tr><td>format</td><td>png</td></tr>\n                                    <tr><td>angle</td><td>20</td></tr>\n                                    <tr><td colspan=\"2\">and then</td></tr>\n                                    <tr><td>crop</td><td>fill</td></tr>\n                                    <tr><td>gravity</td><td>north</td></tr>\n                                    <tr><td>effect</td><td>sepia</td></tr>\n                                    <tr><td>width</td><td>150</td></tr>\n                                    <tr><td>height</td><td>150</td></tr>\n                                </table>\n                                <br/>\n                            </td>\n                        </table>\n\n                        <div class=\"note\">\n                            Take a look at our documentation of <a href=\"http://cloudinary.com/documentation/image_transformations\" target=\"_blank\">Image Transformations</a> for a full list of supported transformations.\n                        </div>\n                    </div>\n                </c:if>\n                <c:if test=\"${!photo.isImage}\">\n                    <a href=\"<cl:url storedSrc=\"${photo}\"/>\" target=\"_blank\">Non Image File</a>\n                </c:if>\n            </div>\n        </c:forEach>\n    </c:if>\n</div>\n<script type='text/javascript'>\n    $('.toggle_info').click(function () {\n        $(this).closest('.photo').toggleClass('show_more_info');\n        return false;\n    });\n</script>\n<%@include file=\"post.jsp\"%>"
  },
  {
    "path": "samples/photo_album_gae/src/main/webapp/WEB-INF/pages/post.jsp",
    "content": "</div>\n</body>\n</html>\n"
  },
  {
    "path": "samples/photo_album_gae/src/main/webapp/WEB-INF/pages/pre.jsp",
    "content": "<%@taglib uri=\"http://java.sun.com/jsp/jstl/core\" prefix=\"c\" %>\n<%@taglib uri=\"http://cloudinary.com/jsp/taglib\" prefix=\"cl\" %>\n<html>\n<head>\n    <title>Photo Album</title>\n    <link type=\"text/css\" rel=\"stylesheet\" media=\"all\" href=\"${pageContext.request.contextPath}/stylesheets/application.css\">\n    <link rel=\"shortcut icon\"\n          href=\"<cl:url src=\"http://cloudinary.com/favicon.png\" type=\"fetch\" effect=\"sepia\"/>\" />\n    <script src=\"http://code.jquery.com/jquery-1.10.1.min.js\"></script>\n</head>\n<body>\n\n<div id=\"logo\">\n    <!-- This will render the image fetched from a remote HTTP URL using Cloudinary -->\n    <cl:image src=\"http://cloudinary.com/images/logo.png\" type=\"fetch\"/>\n</div>\n\n<div class=\"content\">"
  },
  {
    "path": "samples/photo_album_gae/src/main/webapp/WEB-INF/pages/upload.jsp",
    "content": "<!doctype html>\n<%@taglib uri=\"http://www.springframework.org/tags\" prefix=\"spring\" %>\n<%@taglib uri=\"http://www.springframework.org/tags/form\" prefix=\"form\" %>\n<%@taglib uri=\"http://java.sun.com/jsp/jstl/core\" prefix=\"c\" %>\n\n<%@include file=\"pre.jsp\"%>\n\n<h1>Your photo was uploaded sucessfully!</h1>\n\n<c:if test=\"${!empty photo}\">\n<div class=\"photo\">\n    <h2>${photo.title}</h2>\n    <a href=\"<cl:url storedSrc=\"${photo}\"/>\" target=\"_blank\">\n        <c:if test=\"${photo.isImage}\">\n            <cl:image storedSrc=\"${photo}\" extraClasses=\"thumbnail inline\" />\n        </c:if>\n        <c:if test=\"${!photo.isImage}\">\n            Non image file\n        </c:if>\n    </a>\n</div>\n</c:if>\n\n<a href=\"<c:url value=\"/\"/>\" class=\"back_link\">Back to list</a>\n\n<c:if test=\"${!empty upload}\">\n<div class=\"upload_details\">\n    <h2>Upload metadata:</h2>\n    <table>\n        <c:forEach var=\"entry\" items=\"${upload}\">\n            <tr><td>${entry.key}</td><td>${entry.value}</td></tr>\n        </c:forEach>\n    </table>\n</div>\n</c:if>\n\n<%@include file=\"post.jsp\"%>"
  },
  {
    "path": "samples/photo_album_gae/src/main/webapp/WEB-INF/pages/upload_form.jsp",
    "content": "<!doctype html>\n<%@taglib uri=\"http://www.springframework.org/tags\" prefix=\"spring\" %>\n<%@taglib uri=\"http://www.springframework.org/tags/form\" prefix=\"form\" %>\n<%@taglib uri=\"http://java.sun.com/jsp/jstl/core\" prefix=\"c\" %>\n\n<%@include file=\"pre.jsp\"%>\n<!-- A standard form for uploading images to your server -->\n<div id='backend_upload'>\n    <h1>New Photo</h1>\n    <h2>Image file is uploaded through the server</h2>\n    <form:form method=\"post\" action=\"upload\" commandName=\"photo\" enctype=\"multipart/form-data\">\n        <div class=\"form_line\">\n            <form:label path=\"title\">Title:</form:label>\n            <div class=\"form_controls\">\n                <form:input path=\"title\"/>\n                <form:errors path=\"title\" extraClasses=\"error\" />\n            </div>\n        </div>\n\n        <c:if test=\"${empty photo.publicId}\">\n            <div class=\"form_line\">\n                <label for=\"file\">Image:</label>\n                <div class=\"form_controls\">\n                    <input type=\"file\" name=\"file\" id=\"file\"/>\n                </div>\n            </div>\n        </c:if>\n        <c:if test=\"${!empty photo.publicId}\">\n            <c:if test=\"${photo.isImage}\">\n                <div class=\"form_line\">\n                    <label>Image:</label>\n                    <div class=\"form_controls\">\n                        <img src=\"${photo.thumbnailUrl}\"/>\n                    </div>\n                </div>\n            </c:if>\n            <c:if test=\"${!photo.isImage}\">\n                <div class=\"form_line\">\n                    <label>Raw file:</label>\n                    <div class=\"form_controls\">\n                        <a href=\"<cl:url storedSrc=\"${photo}\"/>\">${photo.publicId}</a>\n                    </div>\n                </div>\n            </c:if>\n        </c:if>\n        <div class=\"form_line\">\n            <div class=\"form_controls\">\n                <input type=\"submit\" value=\"Submit Photo\"/>\n            </div>\n        </div>\n        <form:hidden path=\"preloadedFile\"/>\n        <form:errors path=\"signature\" extraClasses=\"error\" />\n    </form:form>\n\n</div>\n\n<a href=\"<c:url value=\"/\"/>\" class=\"back_link\">Back to list</a>\n<%@include file=\"post.jsp\"%>\n\n"
  },
  {
    "path": "samples/photo_album_gae/src/main/webapp/WEB-INF/web.xml",
    "content": "<web-app version=\"2.4\"\n         xmlns=\"http://java.sun.com/xml/ns/j2ee\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://java.sun.com/xml/ns/j2ee\n\thttp://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd\">\n\n\t<display-name>Spring MVC Application</display-name>\n\n    <servlet>\n\t\t<servlet-name>mvc-dispatcher</servlet-name>\n\t\t<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>\n        <load-on-startup>1</load-on-startup>\n\t</servlet>\n\n\t<servlet-mapping>\n\t\t<servlet-name>mvc-dispatcher</servlet-name>\n\t\t<url-pattern>/</url-pattern>\n\t</servlet-mapping>\n</web-app>"
  },
  {
    "path": "samples/photo_album_gae/src/main/webapp/assets/cloudinary_cors.html",
    "content": "<!DOCTYPE HTML>\n<html>\n<head>\n  <meta charset=\"utf-8\">\n</head>\n<body>\n  <script>\n/*\n    json2.js\n    2011-10-19\n\n    Public Domain.\n\n    NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.\n\n    See http://www.JSON.org/js.html\n\n    This code should be minified before deployment.\n    See http://javascript.crockford.com/jsmin.html\n\n    USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO\n    NOT CONTROL.\n\n*/\nvar JSON;if(!JSON){JSON={}}(function(){function str(a,b){var c,d,e,f,g=gap,h,i=b[a];if(i&&typeof i===\"object\"&&typeof i.toJSON===\"function\"){i=i.toJSON(a)}if(typeof rep===\"function\"){i=rep.call(b,a,i)}switch(typeof i){case\"string\":return quote(i);case\"number\":return isFinite(i)?String(i):\"null\";case\"boolean\":case\"null\":return String(i);case\"object\":if(!i){return\"null\"}gap+=indent;h=[];if(Object.prototype.toString.apply(i)===\"[object Array]\"){f=i.length;for(c=0;c<f;c+=1){h[c]=str(c,i)||\"null\"}e=h.length===0?\"[]\":gap?\"[\\n\"+gap+h.join(\",\\n\"+gap)+\"\\n\"+g+\"]\":\"[\"+h.join(\",\")+\"]\";gap=g;return e}if(rep&&typeof rep===\"object\"){f=rep.length;for(c=0;c<f;c+=1){if(typeof rep[c]===\"string\"){d=rep[c];e=str(d,i);if(e){h.push(quote(d)+(gap?\": \":\":\")+e)}}}}else{for(d in i){if(Object.prototype.hasOwnProperty.call(i,d)){e=str(d,i);if(e){h.push(quote(d)+(gap?\": \":\":\")+e)}}}}e=h.length===0?\"{}\":gap?\"{\\n\"+gap+h.join(\",\\n\"+gap)+\"\\n\"+g+\"}\":\"{\"+h.join(\",\")+\"}\";gap=g;return e}}function quote(a){escapable.lastIndex=0;return escapable.test(a)?'\"'+a.replace(escapable,function(a){var b=meta[a];return typeof b===\"string\"?b:\"\\\\u\"+(\"0000\"+a.charCodeAt(0).toString(16)).slice(-4)})+'\"':'\"'+a+'\"'}function f(a){return a<10?\"0\"+a:a}\"use strict\";if(typeof Date.prototype.toJSON!==\"function\"){Date.prototype.toJSON=function(a){return isFinite(this.valueOf())?this.getUTCFullYear()+\"-\"+f(this.getUTCMonth()+1)+\"-\"+f(this.getUTCDate())+\"T\"+f(this.getUTCHours())+\":\"+f(this.getUTCMinutes())+\":\"+f(this.getUTCSeconds())+\"Z\":null};String.prototype.toJSON=Number.prototype.toJSON=Boolean.prototype.toJSON=function(a){return this.valueOf()}}var cx=/[\\u0000\\u00ad\\u0600-\\u0604\\u070f\\u17b4\\u17b5\\u200c-\\u200f\\u2028-\\u202f\\u2060-\\u206f\\ufeff\\ufff0-\\uffff]/g,escapable=/[\\\\\\\"\\x00-\\x1f\\x7f-\\x9f\\u00ad\\u0600-\\u0604\\u070f\\u17b4\\u17b5\\u200c-\\u200f\\u2028-\\u202f\\u2060-\\u206f\\ufeff\\ufff0-\\uffff]/g,gap,indent,meta={\"\\b\":\"\\\\b\",\"\\t\":\"\\\\t\",\"\\n\":\"\\\\n\",\"\\f\":\"\\\\f\",\"\\r\":\"\\\\r\",'\"':'\\\\\"',\"\\\\\":\"\\\\\\\\\"},rep;if(typeof JSON.stringify!==\"function\"){JSON.stringify=function(a,b,c){var d;gap=\"\";indent=\"\";if(typeof c===\"number\"){for(d=0;d<c;d+=1){indent+=\" \"}}else if(typeof c===\"string\"){indent=c}rep=b;if(b&&typeof b!==\"function\"&&(typeof b!==\"object\"||typeof b.length!==\"number\")){throw new Error(\"JSON.stringify\")}return str(\"\",{\"\":a})}}if(typeof JSON.parse!==\"function\"){JSON.parse=function(text,reviver){function walk(a,b){var c,d,e=a[b];if(e&&typeof e===\"object\"){for(c in e){if(Object.prototype.hasOwnProperty.call(e,c)){d=walk(e,c);if(d!==undefined){e[c]=d}else{delete e[c]}}}}return reviver.call(a,b,e)}var j;text=String(text);cx.lastIndex=0;if(cx.test(text)){text=text.replace(cx,function(a){return\"\\\\u\"+(\"0000\"+a.charCodeAt(0).toString(16)).slice(-4)})}if(/^[\\],:{}\\s]*$/.test(text.replace(/\\\\(?:[\"\\\\\\/bfnrt]|u[0-9a-fA-F]{4})/g,\"@\").replace(/\"[^\"\\\\\\n\\r]*\"|true|false|null|-?\\d+(?:\\.\\d*)?(?:[eE][+\\-]?\\d+)?/g,\"]\").replace(/(?:^|:|,)(?:\\s*\\[)+/g,\"\"))){j=eval(\"(\"+text+\")\");return typeof reviver===\"function\"?walk({\"\":j},\"\"):j}throw new SyntaxError(\"JSON.parse\")}}})()\n/* end of json2.js */\n\n    function parse(query) {\n      var result = {};\n      var params = query.split(\"&\");\n      for (var i = 0; i < params.length; i++) {\n        var param = params[i].split(\"=\");\n        result[param[0]] = decodeURIComponent(param[1]);\n      }\n      return JSON.stringify(result);\n    }\n    document.body.innerHTML=parse(window.location.search.slice(1));\n  </script>\n</body>\n</html>\n\n"
  },
  {
    "path": "samples/photo_album_gae/src/main/webapp/assets/javascripts/cloudinary/jquery.cloudinary.js",
    "content": "/*\n * Cloudinary's jQuery library - v1.0.10\n * Copyright Cloudinary\n * see https://github.com/cloudinary/cloudinary_js\n */\n\n(function( $ ) {\n  var CF_SHARED_CDN = \"d3jpl91pxevbkh.cloudfront.net\";\n  var OLD_AKAMAI_SHARED_CDN = \"cloudinary-a.akamaihd.net\";\n  var AKAMAI_SHARED_CDN = \"res.cloudinary.com\";\n  var SHARED_CDN = AKAMAI_SHARED_CDN;\n  \n  function utf8_encode (argString) {\n      // http://kevin.vanzonneveld.net\n      // +   original by: Webtoolkit.info (http://www.webtoolkit.info/)\n      // +   improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)\n      // +   improved by: sowberry\n      // +    tweaked by: Jack\n      // +   bugfixed by: Onno Marsman\n      // +   improved by: Yves Sucaet\n      // +   bugfixed by: Onno Marsman\n      // +   bugfixed by: Ulrich\n      // +   bugfixed by: Rafal Kukawski\n      // +   improved by: kirilloid\n      // *     example 1: utf8_encode('Kevin van Zonneveld');\n      // *     returns 1: 'Kevin van Zonneveld'\n  \n      if (argString === null || typeof argString === \"undefined\") {\n          return \"\";\n      }\n  \n      var string = (argString + ''); // .replace(/\\r\\n/g, \"\\n\").replace(/\\r/g, \"\\n\");\n      var utftext = '',\n          start, end, stringl = 0;\n  \n      start = end = 0;\n      stringl = string.length;\n      for (var n = 0; n < stringl; n++) {\n          var c1 = string.charCodeAt(n);\n          var enc = null;\n  \n          if (c1 < 128) {\n              end++;\n          } else if (c1 > 127 && c1 < 2048) {\n              enc = String.fromCharCode((c1 >> 6) | 192, (c1 & 63) | 128);\n          } else {\n              enc = String.fromCharCode((c1 >> 12) | 224, ((c1 >> 6) & 63) | 128, (c1 & 63) | 128);\n          }\n          if (enc !== null) {\n              if (end > start) {\n                  utftext += string.slice(start, end);\n              }\n              utftext += enc;\n              start = end = n + 1;\n          }\n      }\n  \n      if (end > start) {\n          utftext += string.slice(start, stringl);\n      }\n  \n      return utftext;\n  }\n  \n  function crc32 (str) {\n      // http://kevin.vanzonneveld.net\n      // +   original by: Webtoolkit.info (http://www.webtoolkit.info/)\n      // +   improved by: T0bsn\n      // +   improved by: http://stackoverflow.com/questions/2647935/javascript-crc32-function-and-php-crc32-not-matching\n      // -    depends on: utf8_encode\n      // *     example 1: crc32('Kevin van Zonneveld');\n      // *     returns 1: 1249991249\n      str = utf8_encode(str);\n      var table = \"00000000 77073096 EE0E612C 990951BA 076DC419 706AF48F E963A535 9E6495A3 0EDB8832 79DCB8A4 E0D5E91E 97D2D988 09B64C2B 7EB17CBD E7B82D07 90BF1D91 1DB71064 6AB020F2 F3B97148 84BE41DE 1ADAD47D 6DDDE4EB F4D4B551 83D385C7 136C9856 646BA8C0 FD62F97A 8A65C9EC 14015C4F 63066CD9 FA0F3D63 8D080DF5 3B6E20C8 4C69105E D56041E4 A2677172 3C03E4D1 4B04D447 D20D85FD A50AB56B 35B5A8FA 42B2986C DBBBC9D6 ACBCF940 32D86CE3 45DF5C75 DCD60DCF ABD13D59 26D930AC 51DE003A C8D75180 BFD06116 21B4F4B5 56B3C423 CFBA9599 B8BDA50F 2802B89E 5F058808 C60CD9B2 B10BE924 2F6F7C87 58684C11 C1611DAB B6662D3D 76DC4190 01DB7106 98D220BC EFD5102A 71B18589 06B6B51F 9FBFE4A5 E8B8D433 7807C9A2 0F00F934 9609A88E E10E9818 7F6A0DBB 086D3D2D 91646C97 E6635C01 6B6B51F4 1C6C6162 856530D8 F262004E 6C0695ED 1B01A57B 8208F4C1 F50FC457 65B0D9C6 12B7E950 8BBEB8EA FCB9887C 62DD1DDF 15DA2D49 8CD37CF3 FBD44C65 4DB26158 3AB551CE A3BC0074 D4BB30E2 4ADFA541 3DD895D7 A4D1C46D D3D6F4FB 4369E96A 346ED9FC AD678846 DA60B8D0 44042D73 33031DE5 AA0A4C5F DD0D7CC9 5005713C 270241AA BE0B1010 C90C2086 5768B525 206F85B3 B966D409 CE61E49F 5EDEF90E 29D9C998 B0D09822 C7D7A8B4 59B33D17 2EB40D81 B7BD5C3B C0BA6CAD EDB88320 9ABFB3B6 03B6E20C 74B1D29A EAD54739 9DD277AF 04DB2615 73DC1683 E3630B12 94643B84 0D6D6A3E 7A6A5AA8 E40ECF0B 9309FF9D 0A00AE27 7D079EB1 F00F9344 8708A3D2 1E01F268 6906C2FE F762575D 806567CB 196C3671 6E6B06E7 FED41B76 89D32BE0 10DA7A5A 67DD4ACC F9B9DF6F 8EBEEFF9 17B7BE43 60B08ED5 D6D6A3E8 A1D1937E 38D8C2C4 4FDFF252 D1BB67F1 A6BC5767 3FB506DD 48B2364B D80D2BDA AF0A1B4C 36034AF6 41047A60 DF60EFC3 A867DF55 316E8EEF 4669BE79 CB61B38C BC66831A 256FD2A0 5268E236 CC0C7795 BB0B4703 220216B9 5505262F C5BA3BBE B2BD0B28 2BB45A92 5CB36A04 C2D7FFA7 B5D0CF31 2CD99E8B 5BDEAE1D 9B64C2B0 EC63F226 756AA39C 026D930A 9C0906A9 EB0E363F 72076785 05005713 95BF4A82 E2B87A14 7BB12BAE 0CB61B38 92D28E9B E5D5BE0D 7CDCEFB7 0BDBDF21 86D3D2D4 F1D4E242 68DDB3F8 1FDA836E 81BE16CD F6B9265B 6FB077E1 18B74777 88085AE6 FF0F6A70 66063BCA 11010B5C 8F659EFF F862AE69 616BFFD3 166CCF45 A00AE278 D70DD2EE 4E048354 3903B3C2 A7672661 D06016F7 4969474D 3E6E77DB AED16A4A D9D65ADC 40DF0B66 37D83BF0 A9BCAE53 DEBB9EC5 47B2CF7F 30B5FFE9 BDBDF21C CABAC28A 53B39330 24B4A3A6 BAD03605 CDD70693 54DE5729 23D967BF B3667A2E C4614AB8 5D681B02 2A6F2B94 B40BBE37 C30C8EA1 5A05DF1B 2D02EF8D\";\n  \n      var crc = 0;\n      var x = 0;\n      var y = 0;\n  \n      crc = crc ^ (-1);\n      for (var i = 0, iTop = str.length; i < iTop; i++) {\n          y = (crc ^ str.charCodeAt(i)) & 0xFF;\n          x = \"0x\" + table.substr(y * 9, 8);\n          crc = (crc >>> 8) ^ x;\n      }\n  \n      crc = crc ^ (-1);\n      //convert to unsigned 32-bit int if needed\n      if (crc < 0) {crc += 4294967296}\n      return crc;\n  }\n\n  function option_consume(options, option_name, default_value) {\n    var result = options[option_name];\n    delete options[option_name];\n    return typeof(result) == 'undefined' ? default_value : result;\n  }\n  function build_array(arg) {\n    if (!arg) {\n      return [];\n    } else if ($.isArray(arg)) {\n      return arg;\n    } else { \n      return [arg];\n    }\n  }\n  function present(value) {\n    return typeof value != 'undefined' && (\"\" + value).length > 0;\n  } \n  function generate_transformation_string(options) {\n    var width = options['width'];\n    var height = options['height'];\n    var size = option_consume(options, 'size');\n    if (size) {\n      var split_size = size.split(\"x\");\n      options['width'] = width = split_size[0];\n      options['height'] = height = split_size[1];  \n    }       \n    var has_layer = options.overlay || options.underlay;\n     \n    var crop = option_consume(options, 'crop');\n    var angle = build_array(option_consume(options, 'angle')).join(\".\");\n\n    var no_html_sizes = has_layer || present(angle) || crop == \"fit\" || crop == \"limit\" || crop == \"lfill\";\n     \n    if (width && (no_html_sizes || parseFloat(width) < 1)) delete options['width'];\n    if (height && (no_html_sizes || parseFloat(height) < 1)) delete options['height'];\n    if (!crop && !has_layer) width = height = undefined;\n\n    var background = option_consume(options, 'background');\n    background = background && background.replace(/^#/, 'rgb:');\n\n    var base_transformations = build_array(option_consume(options, 'transformation', []));\n    var named_transformation = [];\n    if ($.grep(base_transformations, function(bs) {return typeof(bs) == 'object';}).length > 0) {\n      base_transformations = $.map(base_transformations, function(base_transformation) {\n        return typeof(base_transformation) == 'object' ? generate_transformation_string($.extend({}, base_transformation)) : generate_transformation_string({transformation: base_transformation});\n      });\n    } else {\n      named_transformation = $.grep(base_transformations, function() { return this != null && this != \"\"}).join(\".\");\n      base_transformations = [];\n    }\n    var effect = option_consume(options, \"effect\");\n    if ($.isArray(effect)) effect = effect.join(\":\");\n    \n    var border = option_consume(options, \"border\")\n    if ($.isPlainObject(border)) { \n      var border_width = \"\" + (border.width || 2);\n      var border_color = (border.color || \"black\").replace(/^#/, 'rgb:');\n      border = border_width + \"px_solid_\" + border_color;\n    }\n    \n    var flags = build_array(option_consume(options, 'flags')).join(\".\");\n\n    var params = [['c', crop], ['t', named_transformation], ['w', width], ['h', height], ['b', background], ['e', effect], ['a', angle], ['bo', border], ['fl', flags]];\n    var simple_params = {\n      x: 'x',\n      y: 'y',\n      radius: 'r',\n      gravity: 'g',\n      quality: 'q',\n      prefix: 'p',\n      default_image: 'd',\n      underlay: 'u',\n      overlay: 'l',\n      fetch_format: 'f',\n      density: 'dn',\n      page: 'pg',\n      color_space: 'cl',\n      delay: 'dl',\n      opacity: 'o'\n    };\n    for (var param in simple_params) {\n      params.push([simple_params[param], option_consume(options, param)]);\n    }\n    params.sort(function(a, b){return a[0]<b[0] ? -1  : (a[0]>b[0] ? 1 : 0);});\n    params.push([option_consume(options, 'raw_transformation')]);\n    var transformation = $.map($.grep(params, function(param) {\n      var value = param[param.length-1];\n      return present(value);\n    }), function(param) {\n      return param.join(\"_\");\n    }).join(\",\");\n    base_transformations.push(transformation);\n    return $.grep(base_transformations, present).join(\"/\");\n  }\n  var dummyImg = undefined;\n  function absolutize(url) {\n    if (!dummyImg) dummyImg = document.createElement(\"img\");\n    dummyImg.src = url;\n    url = dummyImg.src;\n    dummyImg.src = null;\n    return url;\n  }\n  function cloudinary_url(public_id, options) { \n    options = options || {};\n    var type = option_consume(options, 'type', 'upload');\n    if (type == 'fetch') {\n      options.fetch_format = options.fetch_format || option_consume(options, 'format');\n    }\n    var transformation = generate_transformation_string(options);\n    var resource_type = option_consume(options, 'resource_type', \"image\");\n    var version = option_consume(options, 'version');\n    var format = option_consume(options, 'format');\n    var cloud_name = option_consume(options, 'cloud_name', $.cloudinary.config().cloud_name);\n    if (!cloud_name) throw \"Unknown cloud_name\";\n    var private_cdn = option_consume(options, 'private_cdn', $.cloudinary.config().private_cdn);    \n    var secure_distribution = option_consume(options, 'secure_distribution', $.cloudinary.config().secure_distribution);    \n    var cname = option_consume(options, 'cname', $.cloudinary.config().cname);\n    var cdn_subdomain = option_consume(options, 'cdn_subdomain', $.cloudinary.config().cdn_subdomain);\n    var shorten = option_consume(options, 'shorten', $.cloudinary.config().shorten);\n    var secure = option_consume(options, 'secure', window.location.protocol == 'https:'); \n\n    if (type == 'fetch') {\n      public_id = absolutize(public_id); \n    }\n    \n    if (public_id.match(/^https?:/)) {\n      if (type == \"upload\" || type == \"asset\") return public_id;\n      public_id = encodeURIComponent(public_id).replace(/%3A/g, \":\").replace(/%2F/g, \"/\"); \n    } else {\n      // Make sure public_id is URI encoded.\n      public_id = encodeURIComponent(decodeURIComponent(public_id)).replace(/%3A/g, \":\").replace(/%2F/g, \"/\");      \n      if (format) {\n        public_id = public_id.replace(/\\.(jpg|png|gif|webp)$/, '') + \".\" + format;\n      }\n    }\n\n    var prefix = window.location.protocol == 'file:' ? \"file://\" : (secure ? 'https://' : 'http://');\n    if (cloud_name.match(/^\\//) && !secure) {    \n      prefix = \"/res\" + cloud_name;\n    } else {\n      var shared_domain = !private_cdn;\n      if (secure) {        \n        if (!secure_distribution || secure_distribution == OLD_AKAMAI_SHARED_CDN) {\n          secure_distribution = private_cdn ? cloud_name + \"-res.cloudinary.com\" : SHARED_CDN;\n        }\n        shared_domain = shared_domain || secure_distribution == SHARED_CDN;\n        prefix += secure_distribution;\n      } else {\n        var subdomain = cdn_subdomain ? \"a\" + ((crc32(public_id) % 5) + 1) + \".\" : \"\";\n        host = cname || (private_cdn ? cloud_name + \"-res.cloudinary.com\" : \"res.cloudinary.com\" );\n        prefix += subdomain + host;\n      }\n      if (shared_domain) prefix += \"/\" + cloud_name;\n    }\n    if (shorten && resource_type == \"image\" && type == \"upload\") {\n      resource_type = \"iu\";\n      type = undefined;\n    }\n    if (public_id.search(\"/\") >= 0 && !public_id.match(/^v[0-9]+/) && !public_id.match(/^https?:\\//) && !present(version)) {\n      version = 1;\n    }\n\n    var url = [prefix, resource_type, type, transformation, version ? \"v\" + version : \"\",\n               public_id].join(\"/\").replace(/([^:])\\/+/g, '$1/');\n    return url;\n  }\n  function html_only_attributes(options) {\n    var width = option_consume(options, 'html_width');\n    var height = option_consume(options, 'html_height');\n    if (width) options['width'] = width;\n    if (height) options['height'] = height;    \n  }\n  var cloudinary_config = undefined;\n  $.cloudinary = {\n    CF_SHARED_CDN: CF_SHARED_CDN,  \n    OLD_AKAMAI_SHARED_CDN: OLD_AKAMAI_SHARED_CDN,\n    AKAMAI_SHARED_CDN: AKAMAI_SHARED_CDN,\n    SHARED_CDN: SHARED_CDN,    \n    config: function(new_config, new_value) {\n      if (!cloudinary_config) {\n        cloudinary_config = {};\n        $('meta[name^=\"cloudinary_\"]').each(function() {\n          cloudinary_config[$(this).attr('name').replace(\"cloudinary_\", '')] = $(this).attr('content');\n        });\n      }\n      if (typeof(new_value) != 'undefined') {\n        cloudinary_config[new_config] = new_value;\n      } else if (typeof(new_config) == 'string') {\n        return cloudinary_config[new_config];\n      } else if (new_config) {\n        cloudinary_config = new_config;\n      }\n      return cloudinary_config;\n    },\n    url: function(public_id, options) {\n      options = $.extend({}, options);\n      return cloudinary_url(public_id, options);    \n    },    \n    url_internal: cloudinary_url,\n    transformation_string: function(options) {\n      options = $.extend({}, options);\n      return generate_transformation_string(options);\n    },\n    image: function(public_id, options) {\n      options = $.extend({}, options);\n      var url = cloudinary_url(public_id, options);\n      html_only_attributes(options);\n      return $('<img/>').attr(options).attr('src', url);      \n    },\n    facebook_profile_image: function(public_id, options) {\n      return $.cloudinary.image(public_id, $.extend({type: 'facebook'}, options));\n    },\n    twitter_profile_image: function(public_id, options) {\n      return $.cloudinary.image(public_id, $.extend({type: 'twitter'}, options));\n    },\n    twitter_name_profile_image: function(public_id, options) {\n      return $.cloudinary.image(public_id, $.extend({type: 'twitter_name'}, options));\n    },\n    gravatar_image: function(public_id, options) {\n      return $.cloudinary.image(public_id, $.extend({type: 'gravatar'}, options));\n    },\n    fetch_image: function(public_id, options) {\n      return $.cloudinary.image(public_id, $.extend({type: 'fetch'}, options));\n    },\n    sprite_css: function(public_id, options) {\n      options = $.extend({type: 'sprite'}, options);\n      if (!public_id.match(/.css$/)) options.format = 'css';\n      return $.cloudinary.url(public_id, options);\n    }\n  };\n  $.fn.cloudinary = function(options) {\n    this.filter('img').each(function() {\n      var img_options = $.extend({width: $(this).attr('width'), height: $(this).attr('height'),\n                          src: $(this).attr('src')},\n                         $.extend($(this).data(), options));\n      var public_id = option_consume(img_options, 'source', option_consume(img_options, 'src')); \n      var url = cloudinary_url(public_id, img_options);\n      html_only_attributes(img_options);\n      $(this).attr({src: url, width: img_options['width'], height: img_options['height']});\n    });\n    return this;\n  };\n  var webp = null;\n  $.fn.webpify = function(options, webp_options) {\n    var that = this;\n    options = options || {};\n    webp_options = webp_options || options;\n    if (!webp) { \n      var webp = $.Deferred();\n      var webp_canary = new Image();\n      webp_canary.onerror = webp.reject;\n      webp_canary.onload = webp.resolve;\n      webp_canary.src = 'data:image/webp;base64,UklGRjIAAABXRUJQVlA4ICYAAACyAgCdASoBAAEALmk0mk0iIiIiIgBoSygABc6zbAAA/v56QAAAAA==';\n    }\n    $(function() {\n      webp.done(function() {\n        $(that).cloudinary($.extend({}, $.extend(webp_options, {format: 'webp'})));\n      }).fail(function() {\n        $(that).cloudinary(options);\n      });\n    });\n  }\n  $.fn.fetchify = function(options) {\n    return this.cloudinary($.extend(options, {'type': 'fetch'}));\n  };\n})( jQuery );\n\n(function( $ ) {\n  if (!$.fn.fileupload) {\n    return;\n  }\n  $.fn.cloudinary_fileupload = function(options) {\n    var initializing = !this.data('blueimpFileupload');\n    options = $.extend({\n      maxFileSize: 20000000,\n      dataType: 'json',\n      headers: {\"X-Requested-With\": \"XMLHttpRequest\"}\n    }, options);\n    this.fileupload(options);\n    \n    if (initializing) {\n      this.bind(\"fileuploaddone\", function(e, data) {\n        if (data.result.error) return;      \n        data.result.path = [\"v\", data.result.version, \"/\", data.result.public_id, \n                            data.result.format ? \".\" + data.result.format : \"\"].join(\"\");\n    \n        if (data.cloudinaryField && data.form.length > 0) {\n          var upload_info = [data.result.resource_type, data.result.type, data.result.path].join(\"/\") + \"#\" + data.result.signature;  \n          var field = $(data.form).find('input[name=\"' + data.cloudinaryField + '\"]');\n          if (field.length > 0) {\n            field.val(upload_info);\n          } else {\n            $('<input></input>').attr({type: \"hidden\", name: data.cloudinaryField}).val(upload_info).appendTo(data.form);\n          }\n        }\n        $(e.target).trigger('cloudinarydone', data);\n      });\n      \n      this.bind(\"fileuploadstart\", function(e){\n        $(e.target).trigger('cloudinarystart');\n      });\n      this.bind(\"fileuploadstop\", function(e){\n        $(e.target).trigger('cloudinarystop');\n      });\n      this.bind(\"fileuploadprogress\", function(e,data){\n        $(e.target).trigger('cloudinaryprogress',data);\n      });\n      this.bind(\"fileuploadprogressall\", function(e,data){\n        $(e.target).trigger('cloudinaryprogressall',data);\n      });\n      this.bind(\"fileuploadfail\", function(e,data){\n        $(e.target).trigger('cloudinaryfail',data);\n      });\n      this.bind(\"fileuploadalways\", function(e,data){\n        $(e.target).trigger('cloudinaryalways',data);\n      });\n\n      if (!this.fileupload('option').url) {\n        var upload_url = \"https://api.cloudinary.com/v1_1/\" + $.cloudinary.config().cloud_name + \"/upload\";\n        this.fileupload('option', 'url', upload_url);\n      }\n    }\n    return this;\n  };\n  \n  $.fn.cloudinary_upload_url = function(remote_url) {\n    this.fileupload('option', 'formData').file = remote_url; \n    this.fileupload('add', { files: [ remote_url ] }); \n    delete(this.fileupload('option', 'formData').file);    \n  }\n  \n  $(function() {\n    $(\"input.cloudinary-fileupload[type=file]\").cloudinary_fileupload();\n  });\n})( jQuery );\n"
  },
  {
    "path": "samples/photo_album_gae/src/main/webapp/assets/javascripts/cloudinary/jquery.fileupload-image.js",
    "content": "/*\n * jQuery File Upload Image Preview & Resize Plugin 1.2.2\n * https://github.com/blueimp/jQuery-File-Upload\n *\n * Copyright 2013, Sebastian Tschan\n * https://blueimp.net\n *\n * Licensed under the MIT license:\n * http://www.opensource.org/licenses/MIT\n */\n\n/*jslint nomen: true, unparam: true, regexp: true */\n/*global define, window, document, DataView, Blob, Uint8Array */\n\n(function (factory) {\n    'use strict';\n    if (typeof define === 'function' && define.amd) {\n        // Register as an anonymous AMD module:\n        define([\n            'jquery',\n            'load-image',\n            'load-image-meta',\n            'load-image-exif',\n            'load-image-ios',\n            'canvas-to-blob',\n            './jquery.fileupload-process'\n        ], factory);\n    } else {\n        // Browser globals:\n        factory(\n            window.jQuery,\n            window.loadImage\n        );\n    }\n}(function ($, loadImage) {\n    'use strict';\n\n    // Prepend to the default processQueue:\n    $.blueimp.fileupload.prototype.options.processQueue.unshift(\n        {\n            action: 'loadImageMetaData',\n            // Always trigger this action,\n            // even if the previous action was rejected: \n            always: true,\n            disableImageHead: '@',\n            disableExif: '@',\n            disableExifThumbnail: '@',\n            disableExifSub: '@',\n            disableExifGps: '@',\n            disabled: '@disableImageMetaDataLoad'\n        },\n        {\n            action: 'loadImage',\n            // Use the action as prefix for the \"@\" options:\n            prefix: true,\n            fileTypes: '@',\n            maxFileSize: '@',\n            noRevoke: '@',\n            disabled: '@disableImageLoad'\n        },\n        {\n            action: 'resizeImage',\n            // Use \"image\" as prefix for the \"@\" options:\n            prefix: 'image',\n            maxWidth: '@',\n            maxHeight: '@',\n            minWidth: '@',\n            minHeight: '@',\n            crop: '@',\n            disabled: '@disableImageResize'\n        },\n        {\n            action: 'saveImage',\n            disabled: '@disableImageResize'\n        },\n        {\n            action: 'saveImageMetaData',\n            disabled: '@disableImageMetaDataSave'\n        },\n        {\n            action: 'resizeImage',\n            // Always trigger this action,\n            // even if the previous action was rejected: \n            always: true,\n            // Use \"preview\" as prefix for the \"@\" options:\n            prefix: 'preview',\n            maxWidth: '@',\n            maxHeight: '@',\n            minWidth: '@',\n            minHeight: '@',\n            crop: '@',\n            orientation: '@',\n            thumbnail: '@',\n            canvas: '@',\n            disabled: '@disableImagePreview'\n        },\n        {\n            action: 'setImage',\n            name: '@imagePreviewName',\n            disabled: '@disableImagePreview'\n        }\n    );\n\n    // The File Upload Resize plugin extends the fileupload widget\n    // with image resize functionality:\n    $.widget('blueimp.fileupload', $.blueimp.fileupload, {\n\n        options: {\n            // The regular expression for the types of images to load:\n            // matched against the file type:\n            loadImageFileTypes: /^image\\/(gif|jpeg|png)$/,\n            // The maximum file size of images to load:\n            loadImageMaxFileSize: 10000000, // 10MB\n            // The maximum width of resized images:\n            imageMaxWidth: 1920,\n            // The maximum height of resized images:\n            imageMaxHeight: 1080,\n            // Define if resized images should be cropped or only scaled:\n            imageCrop: false,\n            // Disable the resize image functionality by default:\n            disableImageResize: true,\n            // The maximum width of the preview images:\n            previewMaxWidth: 80,\n            // The maximum height of the preview images:\n            previewMaxHeight: 80,\n            // Defines the preview orientation (1-8) or takes the orientation\n            // value from Exif data if set to true:\n            previewOrientation: true,\n            // Create the preview using the Exif data thumbnail:\n            previewThumbnail: true,\n            // Define if preview images should be cropped or only scaled:\n            previewCrop: false,\n            // Define if preview images should be resized as canvas elements:\n            previewCanvas: true\n        },\n\n        processActions: {\n\n            // Loads the image given via data.files and data.index\n            // as img element if the browser supports canvas.\n            // Accepts the options fileTypes (regular expression)\n            // and maxFileSize (integer) to limit the files to load:\n            loadImage: function (data, options) {\n                if (options.disabled) {\n                    return data;\n                }\n                var that = this,\n                    file = data.files[data.index],\n                    dfd = $.Deferred();\n                if (($.type(options.maxFileSize) === 'number' &&\n                            file.size > options.maxFileSize) ||\n                        (options.fileTypes &&\n                            !options.fileTypes.test(file.type)) ||\n                        !loadImage(\n                            file,\n                            function (img) {\n                                if (!img.src) {\n                                    return dfd.rejectWith(that, [data]);\n                                }\n                                data.img = img;\n                                dfd.resolveWith(that, [data]);\n                            },\n                            options\n                        )) {\n                    dfd.rejectWith(that, [data]);\n                }\n                return dfd.promise();\n            },\n\n            // Resizes the image given as data.canvas or data.img\n            // and updates data.canvas or data.img with the resized image.\n            // Accepts the options maxWidth, maxHeight, minWidth,\n            // minHeight, canvas and crop:\n            resizeImage: function (data, options) {\n                if (options.disabled) {\n                    return data;\n                }\n                var that = this,\n                    dfd = $.Deferred(),\n                    resolve = function (newImg) {\n                        data[newImg.getContext ? 'canvas' : 'img'] = newImg;\n                        dfd.resolveWith(that, [data]);\n                    },\n                    thumbnail,\n                    img,\n                    newImg;\n                options = $.extend({canvas: true}, options);\n                if (data.exif) {\n                    if (options.orientation === true) {\n                        options.orientation = data.exif.get('Orientation');\n                    }\n                    if (options.thumbnail) {\n                        thumbnail = data.exif.get('Thumbnail');\n                        if (thumbnail) {\n                            loadImage(thumbnail, resolve, options);\n                            return dfd.promise();\n                        }\n                    }\n                }\n                img = (options.canvas && data.canvas) || data.img;\n                if (img) {\n                    newImg = loadImage.scale(img, options);\n                    if (newImg.width !== img.width ||\n                            newImg.height !== img.height) {\n                        resolve(newImg);\n                        return dfd.promise();\n                    }\n                }\n                return data;\n            },\n\n            // Saves the processed image given as data.canvas\n            // inplace at data.index of data.files:\n            saveImage: function (data, options) {\n                if (!data.canvas || options.disabled) {\n                    return data;\n                }\n                var that = this,\n                    file = data.files[data.index],\n                    name = file.name,\n                    dfd = $.Deferred(),\n                    callback = function (blob) {\n                        if (!blob.name) {\n                            if (file.type === blob.type) {\n                                blob.name = file.name;\n                            } else if (file.name) {\n                                blob.name = file.name.replace(\n                                    /\\..+$/,\n                                    '.' + blob.type.substr(6)\n                                );\n                            }\n                        }\n                        // Store the created blob at the position\n                        // of the original file in the files list:\n                        data.files[data.index] = blob;\n                        dfd.resolveWith(that, [data]);\n                    };\n                // Use canvas.mozGetAsFile directly, to retain the filename, as\n                // Gecko doesn't support the filename option for FormData.append:\n                if (data.canvas.mozGetAsFile) {\n                    callback(data.canvas.mozGetAsFile(\n                        (/^image\\/(jpeg|png)$/.test(file.type) && name) ||\n                            ((name && name.replace(/\\..+$/, '')) ||\n                                'blob') + '.png',\n                        file.type\n                    ));\n                } else if (data.canvas.toBlob) {\n                    data.canvas.toBlob(callback, file.type);\n                } else {\n                    return data;\n                }\n                return dfd.promise();\n            },\n\n            loadImageMetaData: function (data, options) {\n                if (options.disabled) {\n                    return data;\n                }\n                var that = this,\n                    dfd = $.Deferred();\n                loadImage.parseMetaData(data.files[data.index], function (result) {\n                    $.extend(data, result);\n                    dfd.resolveWith(that, [data]);\n                }, options);\n                return dfd.promise();\n            },\n\n            saveImageMetaData: function (data, options) {\n                if (!(data.imageHead && data.canvas &&\n                        data.canvas.toBlob && !options.disabled)) {\n                    return data;\n                }\n                var file = data.files[data.index],\n                    blob = new Blob([\n                        data.imageHead,\n                        // Resized images always have a head size of 20 bytes,\n                        // including the JPEG marker and a minimal JFIF header:\n                        this._blobSlice.call(file, 20)\n                    ], {type: file.type});\n                blob.name = file.name;\n                data.files[data.index] = blob;\n                return data;\n            },\n\n            // Sets the resized version of the image as a property of the\n            // file object, must be called after \"saveImage\":\n            setImage: function (data, options) {\n                var img = data.canvas || data.img;\n                if (img && !options.disabled) {\n                    data.files[data.index][options.name || 'preview'] = img;\n                }\n                return data;\n            }\n\n        }\n\n    });\n\n}));\n"
  },
  {
    "path": "samples/photo_album_gae/src/main/webapp/assets/javascripts/cloudinary/jquery.fileupload-process.js",
    "content": "/*\n * jQuery File Upload Processing Plugin 1.2.2\n * https://github.com/blueimp/jQuery-File-Upload\n *\n * Copyright 2012, Sebastian Tschan\n * https://blueimp.net\n *\n * Licensed under the MIT license:\n * http://www.opensource.org/licenses/MIT\n */\n\n/*jslint nomen: true, unparam: true */\n/*global define, window */\n\n(function (factory) {\n    'use strict';\n    if (typeof define === 'function' && define.amd) {\n        // Register as an anonymous AMD module:\n        define([\n            'jquery',\n            './jquery.fileupload'\n        ], factory);\n    } else {\n        // Browser globals:\n        factory(\n            window.jQuery\n        );\n    }\n}(function ($) {\n    'use strict';\n\n    var originalAdd = $.blueimp.fileupload.prototype.options.add;\n\n    // The File Upload Processing plugin extends the fileupload widget\n    // with file processing functionality:\n    $.widget('blueimp.fileupload', $.blueimp.fileupload, {\n\n        options: {\n            // The list of processing actions:\n            processQueue: [\n                /*\n                {\n                    action: 'log',\n                    type: 'debug'\n                }\n                */\n            ],\n            add: function (e, data) {\n                var $this = $(this);\n                data.process(function () {\n                    return $this.fileupload('process', data);\n                });\n                originalAdd.call(this, e, data);\n            }\n        },\n\n        processActions: {\n            /*\n            log: function (data, options) {\n                console[options.type](\n                    'Processing \"' + data.files[data.index].name + '\"'\n                );\n            }\n            */\n        },\n\n        _processFile: function (data) {\n            var that = this,\n                dfd = $.Deferred().resolveWith(that, [data]),\n                chain = dfd.promise();\n            this._trigger('process', null, data);\n            $.each(data.processQueue, function (i, settings) {\n                var func = function (data) {\n                    return that.processActions[settings.action].call(\n                        that,\n                        data,\n                        settings\n                    );\n                };\n                chain = chain.pipe(func, settings.always && func);\n            });\n            chain\n                .done(function () {\n                    that._trigger('processdone', null, data);\n                    that._trigger('processalways', null, data);\n                })\n                .fail(function () {\n                    that._trigger('processfail', null, data);\n                    that._trigger('processalways', null, data);\n                });\n            return chain;\n        },\n\n        // Replaces the settings of each processQueue item that\n        // are strings starting with an \"@\", using the remaining\n        // substring as key for the option map,\n        // e.g. \"@autoUpload\" is replaced with options.autoUpload:\n        _transformProcessQueue: function (options) {\n            var processQueue = [];\n            $.each(options.processQueue, function () {\n                var settings = {},\n                    action = this.action,\n                    prefix = this.prefix === true ? action : this.prefix;\n                $.each(this, function (key, value) {\n                    if ($.type(value) === 'string' &&\n                            value.charAt(0) === '@') {\n                        settings[key] = options[\n                            value.slice(1) || (prefix ? prefix +\n                                key.charAt(0).toUpperCase() + key.slice(1) : key)\n                        ];\n                    } else {\n                        settings[key] = value;\n                    }\n\n                });\n                processQueue.push(settings);\n            });\n            options.processQueue = processQueue;\n        },\n\n        // Returns the number of files currently in the processsing queue:\n        processing: function () {\n            return this._processing;\n        },\n\n        // Processes the files given as files property of the data parameter,\n        // returns a Promise object that allows to bind callbacks:\n        process: function (data) {\n            var that = this,\n                options = $.extend({}, this.options, data);\n            if (options.processQueue && options.processQueue.length) {\n                this._transformProcessQueue(options);\n                if (this._processing === 0) {\n                    this._trigger('processstart');\n                }\n                $.each(data.files, function (index) {\n                    var opts = index ? $.extend({}, options) : options,\n                        func = function () {\n                            return that._processFile(opts);\n                        };\n                    opts.index = index;\n                    that._processing += 1;\n                    that._processingQueue = that._processingQueue.pipe(func, func)\n                        .always(function () {\n                            that._processing -= 1;\n                            if (that._processing === 0) {\n                                that._trigger('processstop');\n                            }\n                        });\n                });\n            }\n            return this._processingQueue;\n        },\n\n        _create: function () {\n            this._super();\n            this._processing = 0;\n            this._processingQueue = $.Deferred().resolveWith(this)\n                .promise();\n        }\n\n    });\n\n}));\n"
  },
  {
    "path": "samples/photo_album_gae/src/main/webapp/assets/javascripts/cloudinary/jquery.fileupload-validate.js",
    "content": "/*\n * jQuery File Upload Validation Plugin 1.1\n * https://github.com/blueimp/jQuery-File-Upload\n *\n * Copyright 2013, Sebastian Tschan\n * https://blueimp.net\n *\n * Licensed under the MIT license:\n * http://www.opensource.org/licenses/MIT\n */\n\n/*jslint nomen: true, unparam: true, regexp: true */\n/*global define, window */\n\n(function (factory) {\n    'use strict';\n    if (typeof define === 'function' && define.amd) {\n        // Register as an anonymous AMD module:\n        define([\n            'jquery',\n            './jquery.fileupload-process'\n        ], factory);\n    } else {\n        // Browser globals:\n        factory(\n            window.jQuery\n        );\n    }\n}(function ($) {\n    'use strict';\n\n    // Append to the default processQueue:\n    $.blueimp.fileupload.prototype.options.processQueue.push(\n        {\n            action: 'validate',\n            // Always trigger this action,\n            // even if the previous action was rejected: \n            always: true,\n            // Options taken from the global options map:\n            acceptFileTypes: '@',\n            maxFileSize: '@',\n            minFileSize: '@',\n            maxNumberOfFiles: '@',\n            disabled: '@disableValidation'\n        }\n    );\n\n    // The File Upload Validation plugin extends the fileupload widget\n    // with file validation functionality:\n    $.widget('blueimp.fileupload', $.blueimp.fileupload, {\n\n        options: {\n            /*\n            // The regular expression for allowed file types, matches\n            // against either file type or file name:\n            acceptFileTypes: /(\\.|\\/)(gif|jpe?g|png)$/i,\n            // The maximum allowed file size in bytes:\n            maxFileSize: 10000000, // 10 MB\n            // The minimum allowed file size in bytes:\n            minFileSize: undefined, // No minimal file size\n            // The limit of files to be uploaded:\n            maxNumberOfFiles: 10,\n            */\n\n            // Function returning the current number of files,\n            // has to be overriden for maxNumberOfFiles validation:\n            getNumberOfFiles: $.noop,\n\n            // Error and info messages:\n            messages: {\n                maxNumberOfFiles: 'Maximum number of files exceeded',\n                acceptFileTypes: 'File type not allowed',\n                maxFileSize: 'File is too large',\n                minFileSize: 'File is too small'\n            }\n        },\n\n        processActions: {\n\n            validate: function (data, options) {\n                if (options.disabled) {\n                    return data;\n                }\n                var dfd = $.Deferred(),\n                    settings = this.options,\n                    file = data.files[data.index],\n                    numberOfFiles = settings.getNumberOfFiles();\n                if (numberOfFiles && $.type(options.maxNumberOfFiles) === 'number' &&\n                        numberOfFiles + data.files.length > options.maxNumberOfFiles) {\n                    file.error = settings.i18n('maxNumberOfFiles');\n                } else if (options.acceptFileTypes &&\n                        !(options.acceptFileTypes.test(file.type) ||\n                        options.acceptFileTypes.test(file.name))) {\n                    file.error = settings.i18n('acceptFileTypes');\n                } else if (options.maxFileSize && file.size > options.maxFileSize) {\n                    file.error = settings.i18n('maxFileSize');\n                } else if ($.type(file.size) === 'number' &&\n                        file.size < options.minFileSize) {\n                    file.error = settings.i18n('minFileSize');\n                } else {\n                    delete file.error;\n                }\n                if (file.error || data.files.error) {\n                    data.files.error = true;\n                    dfd.rejectWith(this, [data]);\n                } else {\n                    dfd.resolveWith(this, [data]);\n                }\n                return dfd.promise();\n            }\n\n        }\n\n    });\n\n}));\n"
  },
  {
    "path": "samples/photo_album_gae/src/main/webapp/assets/javascripts/cloudinary/jquery.fileupload.js",
    "content": "/*\n * jQuery File Upload Plugin 5.31.6\n * https://github.com/blueimp/jQuery-File-Upload\n *\n * Copyright 2010, Sebastian Tschan\n * https://blueimp.net\n *\n * Licensed under the MIT license:\n * http://www.opensource.org/licenses/MIT\n */\n\n/*jslint nomen: true, unparam: true, regexp: true */\n/*global define, window, document, location, File, Blob, FormData */\n\n(function (factory) {\n    'use strict';\n    if (typeof define === 'function' && define.amd) {\n        // Register as an anonymous AMD module:\n        define([\n            'jquery',\n            'jquery.ui.widget'\n        ], factory);\n    } else {\n        // Browser globals:\n        factory(window.jQuery);\n    }\n}(function ($) {\n    'use strict';\n\n    // The FileReader API is not actually used, but works as feature detection,\n    // as e.g. Safari supports XHR file uploads via the FormData API,\n    // but not non-multipart XHR file uploads:\n    $.support.xhrFileUpload = !!(window.XMLHttpRequestUpload && window.FileReader);\n    $.support.xhrFormDataFileUpload = !!window.FormData;\n\n    // Detect support for Blob slicing (required for chunked uploads):\n    $.support.blobSlice = window.Blob && (Blob.prototype.slice ||\n        Blob.prototype.webkitSlice || Blob.prototype.mozSlice);\n\n    // The fileupload widget listens for change events on file input fields defined\n    // via fileInput setting and paste or drop events of the given dropZone.\n    // In addition to the default jQuery Widget methods, the fileupload widget\n    // exposes the \"add\" and \"send\" methods, to add or directly send files using\n    // the fileupload API.\n    // By default, files added via file input selection, paste, drag & drop or\n    // \"add\" method are uploaded immediately, but it is possible to override\n    // the \"add\" callback option to queue file uploads.\n    $.widget('blueimp.fileupload', {\n\n        options: {\n            // The drop target element(s), by the default the complete document.\n            // Set to null to disable drag & drop support:\n            dropZone: $(document),\n            // The paste target element(s), by the default the complete document.\n            // Set to null to disable paste support:\n            pasteZone: $(document),\n            // The file input field(s), that are listened to for change events.\n            // If undefined, it is set to the file input fields inside\n            // of the widget element on plugin initialization.\n            // Set to null to disable the change listener.\n            fileInput: undefined,\n            // By default, the file input field is replaced with a clone after\n            // each input field change event. This is required for iframe transport\n            // queues and allows change events to be fired for the same file\n            // selection, but can be disabled by setting the following option to false:\n            replaceFileInput: true,\n            // The parameter name for the file form data (the request argument name).\n            // If undefined or empty, the name property of the file input field is\n            // used, or \"files[]\" if the file input name property is also empty,\n            // can be a string or an array of strings:\n            paramName: undefined,\n            // By default, each file of a selection is uploaded using an individual\n            // request for XHR type uploads. Set to false to upload file\n            // selections in one request each:\n            singleFileUploads: true,\n            // To limit the number of files uploaded with one XHR request,\n            // set the following option to an integer greater than 0:\n            limitMultiFileUploads: undefined,\n            // Set the following option to true to issue all file upload requests\n            // in a sequential order:\n            sequentialUploads: false,\n            // To limit the number of concurrent uploads,\n            // set the following option to an integer greater than 0:\n            limitConcurrentUploads: undefined,\n            // Set the following option to true to force iframe transport uploads:\n            forceIframeTransport: false,\n            // Set the following option to the location of a redirect url on the\n            // origin server, for cross-domain iframe transport uploads:\n            redirect: undefined,\n            // The parameter name for the redirect url, sent as part of the form\n            // data and set to 'redirect' if this option is empty:\n            redirectParamName: undefined,\n            // Set the following option to the location of a postMessage window,\n            // to enable postMessage transport uploads:\n            postMessage: undefined,\n            // By default, XHR file uploads are sent as multipart/form-data.\n            // The iframe transport is always using multipart/form-data.\n            // Set to false to enable non-multipart XHR uploads:\n            multipart: true,\n            // To upload large files in smaller chunks, set the following option\n            // to a preferred maximum chunk size. If set to 0, null or undefined,\n            // or the browser does not support the required Blob API, files will\n            // be uploaded as a whole.\n            maxChunkSize: undefined,\n            // When a non-multipart upload or a chunked multipart upload has been\n            // aborted, this option can be used to resume the upload by setting\n            // it to the size of the already uploaded bytes. This option is most\n            // useful when modifying the options object inside of the \"add\" or\n            // \"send\" callbacks, as the options are cloned for each file upload.\n            uploadedBytes: undefined,\n            // By default, failed (abort or error) file uploads are removed from the\n            // global progress calculation. Set the following option to false to\n            // prevent recalculating the global progress data:\n            recalculateProgress: true,\n            // Interval in milliseconds to calculate and trigger progress events:\n            progressInterval: 100,\n            // Interval in milliseconds to calculate progress bitrate:\n            bitrateInterval: 500,\n            // By default, uploads are started automatically when adding files:\n            autoUpload: true,\n\n            // Error and info messages:\n            messages: {\n                uploadedBytes: 'Uploaded bytes exceed file size'\n            },\n\n            // Translation function, gets the message key to be translated\n            // and an object with context specific data as arguments:\n            i18n: function (message, context) {\n                message = this.messages[message] || message.toString();\n                if (context) {\n                    $.each(context, function (key, value) {\n                        message = message.replace('{' + key + '}', value);\n                    });\n                }\n                return message;\n            },\n\n            // Additional form data to be sent along with the file uploads can be set\n            // using this option, which accepts an array of objects with name and\n            // value properties, a function returning such an array, a FormData\n            // object (for XHR file uploads), or a simple object.\n            // The form of the first fileInput is given as parameter to the function:\n            formData: function (form) {\n                return form.serializeArray();\n            },\n\n            // The add callback is invoked as soon as files are added to the fileupload\n            // widget (via file input selection, drag & drop, paste or add API call).\n            // If the singleFileUploads option is enabled, this callback will be\n            // called once for each file in the selection for XHR file uploads, else\n            // once for each file selection.\n            //\n            // The upload starts when the submit method is invoked on the data parameter.\n            // The data object contains a files property holding the added files\n            // and allows you to override plugin options as well as define ajax settings.\n            //\n            // Listeners for this callback can also be bound the following way:\n            // .bind('fileuploadadd', func);\n            //\n            // data.submit() returns a Promise object and allows to attach additional\n            // handlers using jQuery's Deferred callbacks:\n            // data.submit().done(func).fail(func).always(func);\n            add: function (e, data) {\n                if (data.autoUpload || (data.autoUpload !== false &&\n                        $(this).fileupload('option', 'autoUpload'))) {\n                    data.process().done(function () {\n                        data.submit();\n                    });\n                }\n            },\n\n            // Other callbacks:\n\n            // Callback for the submit event of each file upload:\n            // submit: function (e, data) {}, // .bind('fileuploadsubmit', func);\n\n            // Callback for the start of each file upload request:\n            // send: function (e, data) {}, // .bind('fileuploadsend', func);\n\n            // Callback for successful uploads:\n            // done: function (e, data) {}, // .bind('fileuploaddone', func);\n\n            // Callback for failed (abort or error) uploads:\n            // fail: function (e, data) {}, // .bind('fileuploadfail', func);\n\n            // Callback for completed (success, abort or error) requests:\n            // always: function (e, data) {}, // .bind('fileuploadalways', func);\n\n            // Callback for upload progress events:\n            // progress: function (e, data) {}, // .bind('fileuploadprogress', func);\n\n            // Callback for global upload progress events:\n            // progressall: function (e, data) {}, // .bind('fileuploadprogressall', func);\n\n            // Callback for uploads start, equivalent to the global ajaxStart event:\n            // start: function (e) {}, // .bind('fileuploadstart', func);\n\n            // Callback for uploads stop, equivalent to the global ajaxStop event:\n            // stop: function (e) {}, // .bind('fileuploadstop', func);\n\n            // Callback for change events of the fileInput(s):\n            // change: function (e, data) {}, // .bind('fileuploadchange', func);\n\n            // Callback for paste events to the pasteZone(s):\n            // paste: function (e, data) {}, // .bind('fileuploadpaste', func);\n\n            // Callback for drop events of the dropZone(s):\n            // drop: function (e, data) {}, // .bind('fileuploaddrop', func);\n\n            // Callback for dragover events of the dropZone(s):\n            // dragover: function (e) {}, // .bind('fileuploaddragover', func);\n\n            // Callback for the start of each chunk upload request:\n            // chunksend: function (e, data) {}, // .bind('fileuploadchunksend', func);\n\n            // Callback for successful chunk uploads:\n            // chunkdone: function (e, data) {}, // .bind('fileuploadchunkdone', func);\n\n            // Callback for failed (abort or error) chunk uploads:\n            // chunkfail: function (e, data) {}, // .bind('fileuploadchunkfail', func);\n\n            // Callback for completed (success, abort or error) chunk upload requests:\n            // chunkalways: function (e, data) {}, // .bind('fileuploadchunkalways', func);\n\n            // The plugin options are used as settings object for the ajax calls.\n            // The following are jQuery ajax settings required for the file uploads:\n            processData: false,\n            contentType: false,\n            cache: false\n        },\n\n        // A list of options that require reinitializing event listeners and/or\n        // special initialization code:\n        _specialOptions: [\n            'fileInput',\n            'dropZone',\n            'pasteZone',\n            'multipart',\n            'forceIframeTransport'\n        ],\n\n        _blobSlice: $.support.blobSlice && function () {\n            var slice = this.slice || this.webkitSlice || this.mozSlice;\n            return slice.apply(this, arguments);\n        },\n\n        _BitrateTimer: function () {\n            this.timestamp = ((Date.now) ? Date.now() : (new Date()).getTime());\n            this.loaded = 0;\n            this.bitrate = 0;\n            this.getBitrate = function (now, loaded, interval) {\n                var timeDiff = now - this.timestamp;\n                if (!this.bitrate || !interval || timeDiff > interval) {\n                    this.bitrate = (loaded - this.loaded) * (1000 / timeDiff) * 8;\n                    this.loaded = loaded;\n                    this.timestamp = now;\n                }\n                return this.bitrate;\n            };\n        },\n\n        _isXHRUpload: function (options) {\n            return !options.forceIframeTransport &&\n                ((!options.multipart && $.support.xhrFileUpload) ||\n                $.support.xhrFormDataFileUpload);\n        },\n\n        _getFormData: function (options) {\n            var formData;\n            if (typeof options.formData === 'function') {\n                return options.formData(options.form);\n            }\n            if ($.isArray(options.formData)) {\n                return options.formData;\n            }\n            if ($.type(options.formData) === 'object') {\n                formData = [];\n                $.each(options.formData, function (name, value) {\n                    formData.push({name: name, value: value});\n                });\n                return formData;\n            }\n            return [];\n        },\n\n        _getTotal: function (files) {\n            var total = 0;\n            $.each(files, function (index, file) {\n                total += file.size || 1;\n            });\n            return total;\n        },\n\n        _initProgressObject: function (obj) {\n            var progress = {\n                loaded: 0,\n                total: 0,\n                bitrate: 0\n            };\n            if (obj._progress) {\n                $.extend(obj._progress, progress);\n            } else {\n                obj._progress = progress;\n            }\n        },\n\n        _initResponseObject: function (obj) {\n            var prop;\n            if (obj._response) {\n                for (prop in obj._response) {\n                    if (obj._response.hasOwnProperty(prop)) {\n                        delete obj._response[prop];\n                    }\n                }\n            } else {\n                obj._response = {};\n            }\n        },\n\n        _onProgress: function (e, data) {\n            if (e.lengthComputable) {\n                var now = ((Date.now) ? Date.now() : (new Date()).getTime()),\n                    loaded;\n                if (data._time && data.progressInterval &&\n                        (now - data._time < data.progressInterval) &&\n                        e.loaded !== e.total) {\n                    return;\n                }\n                data._time = now;\n                loaded = Math.floor(\n                    e.loaded / e.total * (data.chunkSize || data._progress.total)\n                ) + (data.uploadedBytes || 0);\n                // Add the difference from the previously loaded state\n                // to the global loaded counter:\n                this._progress.loaded += (loaded - data._progress.loaded);\n                this._progress.bitrate = this._bitrateTimer.getBitrate(\n                    now,\n                    this._progress.loaded,\n                    data.bitrateInterval\n                );\n                data._progress.loaded = data.loaded = loaded;\n                data._progress.bitrate = data.bitrate = data._bitrateTimer.getBitrate(\n                    now,\n                    loaded,\n                    data.bitrateInterval\n                );\n                // Trigger a custom progress event with a total data property set\n                // to the file size(s) of the current upload and a loaded data\n                // property calculated accordingly:\n                this._trigger('progress', e, data);\n                // Trigger a global progress event for all current file uploads,\n                // including ajax calls queued for sequential file uploads:\n                this._trigger('progressall', e, this._progress);\n            }\n        },\n\n        _initProgressListener: function (options) {\n            var that = this,\n                xhr = options.xhr ? options.xhr() : $.ajaxSettings.xhr();\n            // Accesss to the native XHR object is required to add event listeners\n            // for the upload progress event:\n            if (xhr.upload) {\n                $(xhr.upload).bind('progress', function (e) {\n                    var oe = e.originalEvent;\n                    // Make sure the progress event properties get copied over:\n                    e.lengthComputable = oe.lengthComputable;\n                    e.loaded = oe.loaded;\n                    e.total = oe.total;\n                    that._onProgress(e, options);\n                });\n                options.xhr = function () {\n                    return xhr;\n                };\n            }\n        },\n\n        _isInstanceOf: function (type, obj) {\n            // Cross-frame instanceof check\n            return Object.prototype.toString.call(obj) === '[object ' + type + ']';\n        },\n\n        _initXHRData: function (options) {\n            var that = this,\n                formData,\n                file = options.files[0],\n                // Ignore non-multipart setting if not supported:\n                multipart = options.multipart || !$.support.xhrFileUpload,\n                paramName = options.paramName[0];\n            options.headers = options.headers || {};\n            if (options.contentRange) {\n                options.headers['Content-Range'] = options.contentRange;\n            }\n            if (!multipart || options.blob || !this._isInstanceOf('File', file)) {\n                options.headers['Content-Disposition'] = 'attachment; filename=\"' +\n                    encodeURI(file.name) + '\"';\n            }\n            if (!multipart) {\n                options.contentType = file.type;\n                options.data = options.blob || file;\n            } else if ($.support.xhrFormDataFileUpload) {\n                if (options.postMessage) {\n                    // window.postMessage does not allow sending FormData\n                    // objects, so we just add the File/Blob objects to\n                    // the formData array and let the postMessage window\n                    // create the FormData object out of this array:\n                    formData = this._getFormData(options);\n                    if (options.blob) {\n                        formData.push({\n                            name: paramName,\n                            value: options.blob\n                        });\n                    } else {\n                        $.each(options.files, function (index, file) {\n                            formData.push({\n                                name: options.paramName[index] || paramName,\n                                value: file\n                            });\n                        });\n                    }\n                } else {\n                    if (that._isInstanceOf('FormData', options.formData)) {\n                        formData = options.formData;\n                    } else {\n                        formData = new FormData();\n                        $.each(this._getFormData(options), function (index, field) {\n                            formData.append(field.name, field.value);\n                        });\n                    }\n                    if (options.blob) {\n                        formData.append(paramName, options.blob, file.name);\n                    } else {\n                        $.each(options.files, function (index, file) {\n                            // This check allows the tests to run with\n                            // dummy objects:\n                            if (that._isInstanceOf('File', file) ||\n                                    that._isInstanceOf('Blob', file)) {\n                                formData.append(\n                                    options.paramName[index] || paramName,\n                                    file,\n                                    file.name\n                                );\n                            }\n                        });\n                    }\n                }\n                options.data = formData;\n            }\n            // Blob reference is not needed anymore, free memory:\n            options.blob = null;\n        },\n\n        _initIframeSettings: function (options) {\n            var targetHost = $('<a></a>').prop('href', options.url).prop('host');\n            // Setting the dataType to iframe enables the iframe transport:\n            options.dataType = 'iframe ' + (options.dataType || '');\n            // The iframe transport accepts a serialized array as form data:\n            options.formData = this._getFormData(options);\n            // Add redirect url to form data on cross-domain uploads:\n            if (options.redirect && targetHost && targetHost !== location.host) {\n                options.formData.push({\n                    name: options.redirectParamName || 'redirect',\n                    value: options.redirect\n                });\n            }\n        },\n\n        _initDataSettings: function (options) {\n            if (this._isXHRUpload(options)) {\n                if (!this._chunkedUpload(options, true)) {\n                    if (!options.data) {\n                        this._initXHRData(options);\n                    }\n                    this._initProgressListener(options);\n                }\n                if (options.postMessage) {\n                    // Setting the dataType to postmessage enables the\n                    // postMessage transport:\n                    options.dataType = 'postmessage ' + (options.dataType || '');\n                }\n            } else {\n                this._initIframeSettings(options);\n            }\n        },\n\n        _getParamName: function (options) {\n            var fileInput = $(options.fileInput),\n                paramName = options.paramName;\n            if (!paramName) {\n                paramName = [];\n                fileInput.each(function () {\n                    var input = $(this),\n                        name = input.prop('name') || 'files[]',\n                        i = (input.prop('files') || [1]).length;\n                    while (i) {\n                        paramName.push(name);\n                        i -= 1;\n                    }\n                });\n                if (!paramName.length) {\n                    paramName = [fileInput.prop('name') || 'files[]'];\n                }\n            } else if (!$.isArray(paramName)) {\n                paramName = [paramName];\n            }\n            return paramName;\n        },\n\n        _initFormSettings: function (options) {\n            // Retrieve missing options from the input field and the\n            // associated form, if available:\n            if (!options.form || !options.form.length) {\n                options.form = $(options.fileInput.prop('form'));\n                // If the given file input doesn't have an associated form,\n                // use the default widget file input's form:\n                if (!options.form.length) {\n                    options.form = $(this.options.fileInput.prop('form'));\n                }\n            }\n            options.paramName = this._getParamName(options);\n            if (!options.url) {\n                options.url = options.form.prop('action') || location.href;\n            }\n            // The HTTP request method must be \"POST\" or \"PUT\":\n            options.type = (options.type || options.form.prop('method') || '')\n                .toUpperCase();\n            if (options.type !== 'POST' && options.type !== 'PUT' &&\n                    options.type !== 'PATCH') {\n                options.type = 'POST';\n            }\n            if (!options.formAcceptCharset) {\n                options.formAcceptCharset = options.form.attr('accept-charset');\n            }\n        },\n\n        _getAJAXSettings: function (data) {\n            var options = $.extend({}, this.options, data);\n            this._initFormSettings(options);\n            this._initDataSettings(options);\n            return options;\n        },\n\n        // jQuery 1.6 doesn't provide .state(),\n        // while jQuery 1.8+ removed .isRejected() and .isResolved():\n        _getDeferredState: function (deferred) {\n            if (deferred.state) {\n                return deferred.state();\n            }\n            if (deferred.isResolved()) {\n                return 'resolved';\n            }\n            if (deferred.isRejected()) {\n                return 'rejected';\n            }\n            return 'pending';\n        },\n\n        // Maps jqXHR callbacks to the equivalent\n        // methods of the given Promise object:\n        _enhancePromise: function (promise) {\n            promise.success = promise.done;\n            promise.error = promise.fail;\n            promise.complete = promise.always;\n            return promise;\n        },\n\n        // Creates and returns a Promise object enhanced with\n        // the jqXHR methods abort, success, error and complete:\n        _getXHRPromise: function (resolveOrReject, context, args) {\n            var dfd = $.Deferred(),\n                promise = dfd.promise();\n            context = context || this.options.context || promise;\n            if (resolveOrReject === true) {\n                dfd.resolveWith(context, args);\n            } else if (resolveOrReject === false) {\n                dfd.rejectWith(context, args);\n            }\n            promise.abort = dfd.promise;\n            return this._enhancePromise(promise);\n        },\n\n        // Adds convenience methods to the data callback argument:\n        _addConvenienceMethods: function (e, data) {\n            var that = this,\n                getPromise = function (data) {\n                    return $.Deferred().resolveWith(that, [data]).promise();\n                };\n            data.process = function (resolveFunc, rejectFunc) {\n                if (resolveFunc || rejectFunc) {\n                    data._processQueue = this._processQueue =\n                        (this._processQueue || getPromise(this))\n                            .pipe(resolveFunc, rejectFunc);\n                }\n                return this._processQueue || getPromise(this);\n            };\n            data.submit = function () {\n                if (this.state() !== 'pending') {\n                    data.jqXHR = this.jqXHR =\n                        (that._trigger('submit', e, this) !== false) &&\n                        that._onSend(e, this);\n                }\n                return this.jqXHR || that._getXHRPromise();\n            };\n            data.abort = function () {\n                if (this.jqXHR) {\n                    return this.jqXHR.abort();\n                }\n                return that._getXHRPromise();\n            };\n            data.state = function () {\n                if (this.jqXHR) {\n                    return that._getDeferredState(this.jqXHR);\n                }\n                if (this._processQueue) {\n                    return that._getDeferredState(this._processQueue);\n                }\n            };\n            data.progress = function () {\n                return this._progress;\n            };\n            data.response = function () {\n                return this._response;\n            };\n        },\n\n        // Parses the Range header from the server response\n        // and returns the uploaded bytes:\n        _getUploadedBytes: function (jqXHR) {\n            var range = jqXHR.getResponseHeader('Range'),\n                parts = range && range.split('-'),\n                upperBytesPos = parts && parts.length > 1 &&\n                    parseInt(parts[1], 10);\n            return upperBytesPos && upperBytesPos + 1;\n        },\n\n        // Uploads a file in multiple, sequential requests\n        // by splitting the file up in multiple blob chunks.\n        // If the second parameter is true, only tests if the file\n        // should be uploaded in chunks, but does not invoke any\n        // upload requests:\n        _chunkedUpload: function (options, testOnly) {\n            options.uploadedBytes = options.uploadedBytes || 0;\n            var that = this,\n                file = options.files[0],\n                fs = file.size,\n                ub = options.uploadedBytes,\n                mcs = options.maxChunkSize || fs,\n                slice = this._blobSlice,\n                dfd = $.Deferred(),\n                promise = dfd.promise(),\n                jqXHR,\n                upload;\n            if (!(this._isXHRUpload(options) && slice && (ub || mcs < fs)) ||\n                    options.data) {\n                return false;\n            }\n            if (testOnly) {\n                return true;\n            }\n            if (ub >= fs) {\n                file.error = options.i18n('uploadedBytes');\n                return this._getXHRPromise(\n                    false,\n                    options.context,\n                    [null, 'error', file.error]\n                );\n            }\n            // The chunk upload method:\n            upload = function () {\n                // Clone the options object for each chunk upload:\n                var o = $.extend({}, options),\n                    currentLoaded = o._progress.loaded;\n                o.blob = slice.call(\n                    file,\n                    ub,\n                    ub + mcs,\n                    file.type\n                );\n                // Store the current chunk size, as the blob itself\n                // will be dereferenced after data processing:\n                o.chunkSize = o.blob.size;\n                // Expose the chunk bytes position range:\n                o.contentRange = 'bytes ' + ub + '-' +\n                    (ub + o.chunkSize - 1) + '/' + fs;\n                // Process the upload data (the blob and potential form data):\n                that._initXHRData(o);\n                // Add progress listeners for this chunk upload:\n                that._initProgressListener(o);\n                jqXHR = ((that._trigger('chunksend', null, o) !== false && $.ajax(o)) ||\n                        that._getXHRPromise(false, o.context))\n                    .done(function (result, textStatus, jqXHR) {\n                        ub = that._getUploadedBytes(jqXHR) ||\n                            (ub + o.chunkSize);\n                        // Create a progress event if no final progress event\n                        // with loaded equaling total has been triggered\n                        // for this chunk:\n                        if (currentLoaded + o.chunkSize - o._progress.loaded) {\n                            that._onProgress($.Event('progress', {\n                                lengthComputable: true,\n                                loaded: ub - o.uploadedBytes,\n                                total: ub - o.uploadedBytes\n                            }), o);\n                        }\n                        options.uploadedBytes = o.uploadedBytes = ub;\n                        o.result = result;\n                        o.textStatus = textStatus;\n                        o.jqXHR = jqXHR;\n                        that._trigger('chunkdone', null, o);\n                        that._trigger('chunkalways', null, o);\n                        if (ub < fs) {\n                            // File upload not yet complete,\n                            // continue with the next chunk:\n                            upload();\n                        } else {\n                            dfd.resolveWith(\n                                o.context,\n                                [result, textStatus, jqXHR]\n                            );\n                        }\n                    })\n                    .fail(function (jqXHR, textStatus, errorThrown) {\n                        o.jqXHR = jqXHR;\n                        o.textStatus = textStatus;\n                        o.errorThrown = errorThrown;\n                        that._trigger('chunkfail', null, o);\n                        that._trigger('chunkalways', null, o);\n                        dfd.rejectWith(\n                            o.context,\n                            [jqXHR, textStatus, errorThrown]\n                        );\n                    });\n            };\n            this._enhancePromise(promise);\n            promise.abort = function () {\n                return jqXHR.abort();\n            };\n            upload();\n            return promise;\n        },\n\n        _beforeSend: function (e, data) {\n            if (this._active === 0) {\n                // the start callback is triggered when an upload starts\n                // and no other uploads are currently running,\n                // equivalent to the global ajaxStart event:\n                this._trigger('start');\n                // Set timer for global bitrate progress calculation:\n                this._bitrateTimer = new this._BitrateTimer();\n                // Reset the global progress values:\n                this._progress.loaded = this._progress.total = 0;\n                this._progress.bitrate = 0;\n            }\n            // Make sure the container objects for the .response() and\n            // .progress() methods on the data object are available\n            // and reset to their initial state:\n            this._initResponseObject(data);\n            this._initProgressObject(data);\n            data._progress.loaded = data.loaded = data.uploadedBytes || 0;\n            data._progress.total = data.total = this._getTotal(data.files) || 1;\n            data._progress.bitrate = data.bitrate = 0;\n            this._active += 1;\n            // Initialize the global progress values:\n            this._progress.loaded += data.loaded;\n            this._progress.total += data.total;\n        },\n\n        _onDone: function (result, textStatus, jqXHR, options) {\n            var total = options._progress.total,\n                response = options._response;\n            if (options._progress.loaded < total) {\n                // Create a progress event if no final progress event\n                // with loaded equaling total has been triggered:\n                this._onProgress($.Event('progress', {\n                    lengthComputable: true,\n                    loaded: total,\n                    total: total\n                }), options);\n            }\n            response.result = options.result = result;\n            response.textStatus = options.textStatus = textStatus;\n            response.jqXHR = options.jqXHR = jqXHR;\n            this._trigger('done', null, options);\n        },\n\n        _onFail: function (jqXHR, textStatus, errorThrown, options) {\n            var response = options._response;\n            if (options.recalculateProgress) {\n                // Remove the failed (error or abort) file upload from\n                // the global progress calculation:\n                this._progress.loaded -= options._progress.loaded;\n                this._progress.total -= options._progress.total;\n            }\n            response.jqXHR = options.jqXHR = jqXHR;\n            response.textStatus = options.textStatus = textStatus;\n            response.errorThrown = options.errorThrown = errorThrown;\n            this._trigger('fail', null, options);\n        },\n\n        _onAlways: function (jqXHRorResult, textStatus, jqXHRorError, options) {\n            // jqXHRorResult, textStatus and jqXHRorError are added to the\n            // options object via done and fail callbacks\n            this._trigger('always', null, options);\n        },\n\n        _onSend: function (e, data) {\n            if (!data.submit) {\n                this._addConvenienceMethods(e, data);\n            }\n            var that = this,\n                jqXHR,\n                aborted,\n                slot,\n                pipe,\n                options = that._getAJAXSettings(data),\n                send = function () {\n                    that._sending += 1;\n                    // Set timer for bitrate progress calculation:\n                    options._bitrateTimer = new that._BitrateTimer();\n                    jqXHR = jqXHR || (\n                        ((aborted || that._trigger('send', e, options) === false) &&\n                        that._getXHRPromise(false, options.context, aborted)) ||\n                        that._chunkedUpload(options) || $.ajax(options)\n                    ).done(function (result, textStatus, jqXHR) {\n                        that._onDone(result, textStatus, jqXHR, options);\n                    }).fail(function (jqXHR, textStatus, errorThrown) {\n                        that._onFail(jqXHR, textStatus, errorThrown, options);\n                    }).always(function (jqXHRorResult, textStatus, jqXHRorError) {\n                        that._onAlways(\n                            jqXHRorResult,\n                            textStatus,\n                            jqXHRorError,\n                            options\n                        );\n                        that._sending -= 1;\n                        that._active -= 1;\n                        if (options.limitConcurrentUploads &&\n                                options.limitConcurrentUploads > that._sending) {\n                            // Start the next queued upload,\n                            // that has not been aborted:\n                            var nextSlot = that._slots.shift();\n                            while (nextSlot) {\n                                if (that._getDeferredState(nextSlot) === 'pending') {\n                                    nextSlot.resolve();\n                                    break;\n                                }\n                                nextSlot = that._slots.shift();\n                            }\n                        }\n                        if (that._active === 0) {\n                            // The stop callback is triggered when all uploads have\n                            // been completed, equivalent to the global ajaxStop event:\n                            that._trigger('stop');\n                        }\n                    });\n                    return jqXHR;\n                };\n            this._beforeSend(e, options);\n            if (this.options.sequentialUploads ||\n                    (this.options.limitConcurrentUploads &&\n                    this.options.limitConcurrentUploads <= this._sending)) {\n                if (this.options.limitConcurrentUploads > 1) {\n                    slot = $.Deferred();\n                    this._slots.push(slot);\n                    pipe = slot.pipe(send);\n                } else {\n                    this._sequence = this._sequence.pipe(send, send);\n                    pipe = this._sequence;\n                }\n                // Return the piped Promise object, enhanced with an abort method,\n                // which is delegated to the jqXHR object of the current upload,\n                // and jqXHR callbacks mapped to the equivalent Promise methods:\n                pipe.abort = function () {\n                    aborted = [undefined, 'abort', 'abort'];\n                    if (!jqXHR) {\n                        if (slot) {\n                            slot.rejectWith(options.context, aborted);\n                        }\n                        return send();\n                    }\n                    return jqXHR.abort();\n                };\n                return this._enhancePromise(pipe);\n            }\n            return send();\n        },\n\n        _onAdd: function (e, data) {\n            var that = this,\n                result = true,\n                options = $.extend({}, this.options, data),\n                limit = options.limitMultiFileUploads,\n                paramName = this._getParamName(options),\n                paramNameSet,\n                paramNameSlice,\n                fileSet,\n                i;\n            if (!(options.singleFileUploads || limit) ||\n                    !this._isXHRUpload(options)) {\n                fileSet = [data.files];\n                paramNameSet = [paramName];\n            } else if (!options.singleFileUploads && limit) {\n                fileSet = [];\n                paramNameSet = [];\n                for (i = 0; i < data.files.length; i += limit) {\n                    fileSet.push(data.files.slice(i, i + limit));\n                    paramNameSlice = paramName.slice(i, i + limit);\n                    if (!paramNameSlice.length) {\n                        paramNameSlice = paramName;\n                    }\n                    paramNameSet.push(paramNameSlice);\n                }\n            } else {\n                paramNameSet = paramName;\n            }\n            data.originalFiles = data.files;\n            $.each(fileSet || data.files, function (index, element) {\n                var newData = $.extend({}, data);\n                newData.files = fileSet ? element : [element];\n                newData.paramName = paramNameSet[index];\n                that._initResponseObject(newData);\n                that._initProgressObject(newData);\n                that._addConvenienceMethods(e, newData);\n                result = that._trigger('add', e, newData);\n                return result;\n            });\n            return result;\n        },\n\n        _replaceFileInput: function (input) {\n            var inputClone = input.clone(true);\n            $('<form></form>').append(inputClone)[0].reset();\n            // Detaching allows to insert the fileInput on another form\n            // without loosing the file input value:\n            input.after(inputClone).detach();\n            // Avoid memory leaks with the detached file input:\n            $.cleanData(input.unbind('remove'));\n            // Replace the original file input element in the fileInput\n            // elements set with the clone, which has been copied including\n            // event handlers:\n            this.options.fileInput = this.options.fileInput.map(function (i, el) {\n                if (el === input[0]) {\n                    return inputClone[0];\n                }\n                return el;\n            });\n            // If the widget has been initialized on the file input itself,\n            // override this.element with the file input clone:\n            if (input[0] === this.element[0]) {\n                this.element = inputClone;\n            }\n        },\n\n        _handleFileTreeEntry: function (entry, path) {\n            var that = this,\n                dfd = $.Deferred(),\n                errorHandler = function (e) {\n                    if (e && !e.entry) {\n                        e.entry = entry;\n                    }\n                    // Since $.when returns immediately if one\n                    // Deferred is rejected, we use resolve instead.\n                    // This allows valid files and invalid items\n                    // to be returned together in one set:\n                    dfd.resolve([e]);\n                },\n                dirReader;\n            path = path || '';\n            if (entry.isFile) {\n                if (entry._file) {\n                    // Workaround for Chrome bug #149735\n                    entry._file.relativePath = path;\n                    dfd.resolve(entry._file);\n                } else {\n                    entry.file(function (file) {\n                        file.relativePath = path;\n                        dfd.resolve(file);\n                    }, errorHandler);\n                }\n            } else if (entry.isDirectory) {\n                dirReader = entry.createReader();\n                dirReader.readEntries(function (entries) {\n                    that._handleFileTreeEntries(\n                        entries,\n                        path + entry.name + '/'\n                    ).done(function (files) {\n                        dfd.resolve(files);\n                    }).fail(errorHandler);\n                }, errorHandler);\n            } else {\n                // Return an empy list for file system items\n                // other than files or directories:\n                dfd.resolve([]);\n            }\n            return dfd.promise();\n        },\n\n        _handleFileTreeEntries: function (entries, path) {\n            var that = this;\n            return $.when.apply(\n                $,\n                $.map(entries, function (entry) {\n                    return that._handleFileTreeEntry(entry, path);\n                })\n            ).pipe(function () {\n                return Array.prototype.concat.apply(\n                    [],\n                    arguments\n                );\n            });\n        },\n\n        _getDroppedFiles: function (dataTransfer) {\n            dataTransfer = dataTransfer || {};\n            var items = dataTransfer.items;\n            if (items && items.length && (items[0].webkitGetAsEntry ||\n                    items[0].getAsEntry)) {\n                return this._handleFileTreeEntries(\n                    $.map(items, function (item) {\n                        var entry;\n                        if (item.webkitGetAsEntry) {\n                            entry = item.webkitGetAsEntry();\n                            if (entry) {\n                                // Workaround for Chrome bug #149735:\n                                entry._file = item.getAsFile();\n                            }\n                            return entry;\n                        }\n                        return item.getAsEntry();\n                    })\n                );\n            }\n            return $.Deferred().resolve(\n                $.makeArray(dataTransfer.files)\n            ).promise();\n        },\n\n        _getSingleFileInputFiles: function (fileInput) {\n            fileInput = $(fileInput);\n            var entries = fileInput.prop('webkitEntries') ||\n                    fileInput.prop('entries'),\n                files,\n                value;\n            if (entries && entries.length) {\n                return this._handleFileTreeEntries(entries);\n            }\n            files = $.makeArray(fileInput.prop('files'));\n            if (!files.length) {\n                value = fileInput.prop('value');\n                if (!value) {\n                    return $.Deferred().resolve([]).promise();\n                }\n                // If the files property is not available, the browser does not\n                // support the File API and we add a pseudo File object with\n                // the input value as name with path information removed:\n                files = [{name: value.replace(/^.*\\\\/, '')}];\n            } else if (files[0].name === undefined && files[0].fileName) {\n                // File normalization for Safari 4 and Firefox 3:\n                $.each(files, function (index, file) {\n                    file.name = file.fileName;\n                    file.size = file.fileSize;\n                });\n            }\n            return $.Deferred().resolve(files).promise();\n        },\n\n        _getFileInputFiles: function (fileInput) {\n            if (!(fileInput instanceof $) || fileInput.length === 1) {\n                return this._getSingleFileInputFiles(fileInput);\n            }\n            return $.when.apply(\n                $,\n                $.map(fileInput, this._getSingleFileInputFiles)\n            ).pipe(function () {\n                return Array.prototype.concat.apply(\n                    [],\n                    arguments\n                );\n            });\n        },\n\n        _onChange: function (e) {\n            var that = this,\n                data = {\n                    fileInput: $(e.target),\n                    form: $(e.target.form)\n                };\n            this._getFileInputFiles(data.fileInput).always(function (files) {\n                data.files = files;\n                if (that.options.replaceFileInput) {\n                    that._replaceFileInput(data.fileInput);\n                }\n                if (that._trigger('change', e, data) !== false) {\n                    that._onAdd(e, data);\n                }\n            });\n        },\n\n        _onPaste: function (e) {\n            var items = e.originalEvent && e.originalEvent.clipboardData &&\n                    e.originalEvent.clipboardData.items,\n                data = {files: []};\n            if (items && items.length) {\n                $.each(items, function (index, item) {\n                    var file = item.getAsFile && item.getAsFile();\n                    if (file) {\n                        data.files.push(file);\n                    }\n                });\n                if (this._trigger('paste', e, data) === false ||\n                        this._onAdd(e, data) === false) {\n                    return false;\n                }\n            }\n        },\n\n        _onDrop: function (e) {\n            e.dataTransfer = e.originalEvent && e.originalEvent.dataTransfer;\n            var that = this,\n                dataTransfer = e.dataTransfer,\n                data = {};\n            if (dataTransfer && dataTransfer.files && dataTransfer.files.length) {\n                e.preventDefault();\n                this._getDroppedFiles(dataTransfer).always(function (files) {\n                    data.files = files;\n                    if (that._trigger('drop', e, data) !== false) {\n                        that._onAdd(e, data);\n                    }\n                });\n            }\n        },\n\n        _onDragOver: function (e) {\n            e.dataTransfer = e.originalEvent && e.originalEvent.dataTransfer;\n            var dataTransfer = e.dataTransfer;\n            if (dataTransfer) {\n                if (this._trigger('dragover', e) === false) {\n                    return false;\n                }\n                if ($.inArray('Files', dataTransfer.types) !== -1) {\n                    dataTransfer.dropEffect = 'copy';\n                    e.preventDefault();\n                }\n            }\n        },\n\n        _initEventHandlers: function () {\n            if (this._isXHRUpload(this.options)) {\n                this._on(this.options.dropZone, {\n                    dragover: this._onDragOver,\n                    drop: this._onDrop\n                });\n                this._on(this.options.pasteZone, {\n                    paste: this._onPaste\n                });\n            }\n            this._on(this.options.fileInput, {\n                change: this._onChange\n            });\n        },\n\n        _destroyEventHandlers: function () {\n            this._off(this.options.dropZone, 'dragover drop');\n            this._off(this.options.pasteZone, 'paste');\n            this._off(this.options.fileInput, 'change');\n        },\n\n        _setOption: function (key, value) {\n            var reinit = $.inArray(key, this._specialOptions) !== -1;\n            if (reinit) {\n                this._destroyEventHandlers();\n            }\n            this._super(key, value);\n            if (reinit) {\n                this._initSpecialOptions();\n                this._initEventHandlers();\n            }\n        },\n\n        _initSpecialOptions: function () {\n            var options = this.options;\n            if (options.fileInput === undefined) {\n                options.fileInput = this.element.is('input[type=\"file\"]') ?\n                        this.element : this.element.find('input[type=\"file\"]');\n            } else if (!(options.fileInput instanceof $)) {\n                options.fileInput = $(options.fileInput);\n            }\n            if (!(options.dropZone instanceof $)) {\n                options.dropZone = $(options.dropZone);\n            }\n            if (!(options.pasteZone instanceof $)) {\n                options.pasteZone = $(options.pasteZone);\n            }\n        },\n\n        _getRegExp: function (str) {\n            var parts = str.split('/'),\n                modifiers = parts.pop();\n            parts.shift();\n            return new RegExp(parts.join('/'), modifiers);\n        },\n\n        _isRegExpOption: function (key, value) {\n            return key !== 'url' && $.type(value) === 'string' &&\n                /^\\/.*\\/[igm]{0,3}$/.test(value);\n        },\n\n        _initDataAttributes: function () {\n            var that = this,\n                options = this.options;\n            // Initialize options set via HTML5 data-attributes:\n            $.each(\n                $(this.element[0].cloneNode(false)).data(),\n                function (key, value) {\n                    if (that._isRegExpOption(key, value)) {\n                        value = that._getRegExp(value);\n                    }\n                    options[key] = value;\n                }\n            );\n        },\n\n        _create: function () {\n            this._initDataAttributes();\n            this._initSpecialOptions();\n            this._slots = [];\n            this._sequence = this._getXHRPromise(true);\n            this._sending = this._active = 0;\n            this._initProgressObject(this);\n            this._initEventHandlers();\n        },\n\n        // This method is exposed to the widget API and allows to query\n        // the number of active uploads:\n        active: function () {\n            return this._active;\n        },\n\n        // This method is exposed to the widget API and allows to query\n        // the widget upload progress.\n        // It returns an object with loaded, total and bitrate properties\n        // for the running uploads:\n        progress: function () {\n            return this._progress;\n        },\n\n        // This method is exposed to the widget API and allows adding files\n        // using the fileupload API. The data parameter accepts an object which\n        // must have a files property and can contain additional options:\n        // .fileupload('add', {files: filesList});\n        add: function (data) {\n            var that = this;\n            if (!data || this.options.disabled) {\n                return;\n            }\n            if (data.fileInput && !data.files) {\n                this._getFileInputFiles(data.fileInput).always(function (files) {\n                    data.files = files;\n                    that._onAdd(null, data);\n                });\n            } else {\n                data.files = $.makeArray(data.files);\n                this._onAdd(null, data);\n            }\n        },\n\n        // This method is exposed to the widget API and allows sending files\n        // using the fileupload API. The data parameter accepts an object which\n        // must have a files or fileInput property and can contain additional options:\n        // .fileupload('send', {files: filesList});\n        // The method returns a Promise object for the file upload call.\n        send: function (data) {\n            if (data && !this.options.disabled) {\n                if (data.fileInput && !data.files) {\n                    var that = this,\n                        dfd = $.Deferred(),\n                        promise = dfd.promise(),\n                        jqXHR,\n                        aborted;\n                    promise.abort = function () {\n                        aborted = true;\n                        if (jqXHR) {\n                            return jqXHR.abort();\n                        }\n                        dfd.reject(null, 'abort', 'abort');\n                        return promise;\n                    };\n                    this._getFileInputFiles(data.fileInput).always(\n                        function (files) {\n                            if (aborted) {\n                                return;\n                            }\n                            data.files = files;\n                            jqXHR = that._onSend(null, data).then(\n                                function (result, textStatus, jqXHR) {\n                                    dfd.resolve(result, textStatus, jqXHR);\n                                },\n                                function (jqXHR, textStatus, errorThrown) {\n                                    dfd.reject(jqXHR, textStatus, errorThrown);\n                                }\n                            );\n                        }\n                    );\n                    return this._enhancePromise(promise);\n                }\n                data.files = $.makeArray(data.files);\n                if (data.files.length) {\n                    return this._onSend(null, data);\n                }\n            }\n            return this._getXHRPromise(false, data && data.context);\n        }\n\n    });\n\n}));\n"
  },
  {
    "path": "samples/photo_album_gae/src/main/webapp/assets/javascripts/cloudinary/jquery.iframe-transport.js",
    "content": "/*\n * jQuery Iframe Transport Plugin 1.7\n * https://github.com/blueimp/jQuery-File-Upload\n *\n * Copyright 2011, Sebastian Tschan\n * https://blueimp.net\n *\n * Licensed under the MIT license:\n * http://www.opensource.org/licenses/MIT\n */\n\n/*jslint unparam: true, nomen: true */\n/*global define, window, document */\n\n(function (factory) {\n    'use strict';\n    if (typeof define === 'function' && define.amd) {\n        // Register as an anonymous AMD module:\n        define(['jquery'], factory);\n    } else {\n        // Browser globals:\n        factory(window.jQuery);\n    }\n}(function ($) {\n    'use strict';\n\n    // Helper variable to create unique names for the transport iframes:\n    var counter = 0;\n\n    // The iframe transport accepts three additional options:\n    // options.fileInput: a jQuery collection of file input fields\n    // options.paramName: the parameter name for the file form data,\n    //  overrides the name property of the file input field(s),\n    //  can be a string or an array of strings.\n    // options.formData: an array of objects with name and value properties,\n    //  equivalent to the return data of .serializeArray(), e.g.:\n    //  [{name: 'a', value: 1}, {name: 'b', value: 2}]\n    $.ajaxTransport('iframe', function (options) {\n        if (options.async) {\n            var form,\n                iframe,\n                addParamChar;\n            return {\n                send: function (_, completeCallback) {\n                    form = $('<form style=\"display:none;\"></form>');\n                    form.attr('accept-charset', options.formAcceptCharset);\n                    addParamChar = /\\?/.test(options.url) ? '&' : '?';\n                    // XDomainRequest only supports GET and POST:\n                    if (options.type === 'DELETE') {\n                        options.url = options.url + addParamChar + '_method=DELETE';\n                        options.type = 'POST';\n                    } else if (options.type === 'PUT') {\n                        options.url = options.url + addParamChar + '_method=PUT';\n                        options.type = 'POST';\n                    } else if (options.type === 'PATCH') {\n                        options.url = options.url + addParamChar + '_method=PATCH';\n                        options.type = 'POST';\n                    }\n                    // javascript:false as initial iframe src\n                    // prevents warning popups on HTTPS in IE6.\n                    // IE versions below IE8 cannot set the name property of\n                    // elements that have already been added to the DOM,\n                    // so we set the name along with the iframe HTML markup:\n                    counter += 1;\n                    iframe = $(\n                        '<iframe src=\"javascript:false;\" name=\"iframe-transport-' +\n                            counter + '\"></iframe>'\n                    ).bind('load', function () {\n                        var fileInputClones,\n                            paramNames = $.isArray(options.paramName) ?\n                                    options.paramName : [options.paramName];\n                        iframe\n                            .unbind('load')\n                            .bind('load', function () {\n                                var response;\n                                // Wrap in a try/catch block to catch exceptions thrown\n                                // when trying to access cross-domain iframe contents:\n                                try {\n                                    response = iframe.contents();\n                                    // Google Chrome and Firefox do not throw an\n                                    // exception when calling iframe.contents() on\n                                    // cross-domain requests, so we unify the response:\n                                    if (!response.length || !response[0].firstChild) {\n                                        throw new Error();\n                                    }\n                                } catch (e) {\n                                    response = undefined;\n                                }\n                                // The complete callback returns the\n                                // iframe content document as response object:\n                                completeCallback(\n                                    200,\n                                    'success',\n                                    {'iframe': response}\n                                );\n                                // Fix for IE endless progress bar activity bug\n                                // (happens on form submits to iframe targets):\n                                $('<iframe src=\"javascript:false;\"></iframe>')\n                                    .appendTo(form);\n                                window.setTimeout(function () {\n                                    // Removing the form in a setTimeout call\n                                    // allows Chrome's developer tools to display\n                                    // the response result\n                                    form.remove();\n                                }, 0);\n                            });\n                        form\n                            .prop('target', iframe.prop('name'))\n                            .prop('action', options.url)\n                            .prop('method', options.type);\n                        if (options.formData) {\n                            $.each(options.formData, function (index, field) {\n                                $('<input type=\"hidden\"/>')\n                                    .prop('name', field.name)\n                                    .val(field.value)\n                                    .appendTo(form);\n                            });\n                        }\n                        if (options.fileInput && options.fileInput.length &&\n                                options.type === 'POST') {\n                            fileInputClones = options.fileInput.clone();\n                            // Insert a clone for each file input field:\n                            options.fileInput.after(function (index) {\n                                return fileInputClones[index];\n                            });\n                            if (options.paramName) {\n                                options.fileInput.each(function (index) {\n                                    $(this).prop(\n                                        'name',\n                                        paramNames[index] || options.paramName\n                                    );\n                                });\n                            }\n                            // Appending the file input fields to the hidden form\n                            // removes them from their original location:\n                            form\n                                .append(options.fileInput)\n                                .prop('enctype', 'multipart/form-data')\n                                // enctype must be set as encoding for IE:\n                                .prop('encoding', 'multipart/form-data');\n                        }\n                        form.submit();\n                        // Insert the file input fields at their original location\n                        // by replacing the clones with the originals:\n                        if (fileInputClones && fileInputClones.length) {\n                            options.fileInput.each(function (index, input) {\n                                var clone = $(fileInputClones[index]);\n                                $(input).prop('name', clone.prop('name'));\n                                clone.replaceWith(input);\n                            });\n                        }\n                    });\n                    form.append(iframe).appendTo(document.body);\n                },\n                abort: function () {\n                    if (iframe) {\n                        // javascript:false as iframe src aborts the request\n                        // and prevents warning popups on HTTPS in IE6.\n                        // concat is used to avoid the \"Script URL\" JSLint error:\n                        iframe\n                            .unbind('load')\n                            .prop('src', 'javascript'.concat(':false;'));\n                    }\n                    if (form) {\n                        form.remove();\n                    }\n                }\n            };\n        }\n    });\n\n    // The iframe transport returns the iframe content document as response.\n    // The following adds converters from iframe to text, json, html, xml\n    // and script.\n    // Please note that the Content-Type for JSON responses has to be text/plain\n    // or text/html, if the browser doesn't include application/json in the\n    // Accept header, else IE will show a download dialog.\n    // The Content-Type for XML responses on the other hand has to be always\n    // application/xml or text/xml, so IE properly parses the XML response.\n    // See also\n    // https://github.com/blueimp/jQuery-File-Upload/wiki/Setup#content-type-negotiation\n    $.ajaxSetup({\n        converters: {\n            'iframe text': function (iframe) {\n                return iframe && $(iframe[0].body).text();\n            },\n            'iframe json': function (iframe) {\n                return iframe && $.parseJSON($(iframe[0].body).text());\n            },\n            'iframe html': function (iframe) {\n                return iframe && $(iframe[0].body).html();\n            },\n            'iframe xml': function (iframe) {\n                var xmlDoc = iframe && iframe[0];\n                return xmlDoc && $.isXMLDoc(xmlDoc) ? xmlDoc :\n                        $.parseXML((xmlDoc.XMLDocument && xmlDoc.XMLDocument.xml) ||\n                            $(xmlDoc.body).html());\n            },\n            'iframe script': function (iframe) {\n                return iframe && $.globalEval($(iframe[0].body).text());\n            }\n        }\n    });\n\n}));\n"
  },
  {
    "path": "samples/photo_album_gae/src/main/webapp/assets/javascripts/cloudinary/jquery.ui.widget.js",
    "content": "/*\n * jQuery UI Widget 1.10.3+amd\n * https://github.com/blueimp/jQuery-File-Upload\n *\n * Copyright 2013 jQuery Foundation and other contributors\n * Released under the MIT license.\n * http://jquery.org/license\n *\n * http://api.jqueryui.com/jQuery.widget/\n */\n\n(function (factory) {\n    if (typeof define === \"function\" && define.amd) {\n        // Register as an anonymous AMD module:\n        define([\"jquery\"], factory);\n    } else {\n        // Browser globals:\n        factory(jQuery);\n    }\n}(function( $, undefined ) {\n\nvar uuid = 0,\n\tslice = Array.prototype.slice,\n\t_cleanData = $.cleanData;\n$.cleanData = function( elems ) {\n\tfor ( var i = 0, elem; (elem = elems[i]) != null; i++ ) {\n\t\ttry {\n\t\t\t$( elem ).triggerHandler( \"remove\" );\n\t\t// http://bugs.jquery.com/ticket/8235\n\t\t} catch( e ) {}\n\t}\n\t_cleanData( elems );\n};\n\n$.widget = function( name, base, prototype ) {\n\tvar fullName, existingConstructor, constructor, basePrototype,\n\t\t// proxiedPrototype allows the provided prototype to remain unmodified\n\t\t// so that it can be used as a mixin for multiple widgets (#8876)\n\t\tproxiedPrototype = {},\n\t\tnamespace = name.split( \".\" )[ 0 ];\n\n\tname = name.split( \".\" )[ 1 ];\n\tfullName = namespace + \"-\" + name;\n\n\tif ( !prototype ) {\n\t\tprototype = base;\n\t\tbase = $.Widget;\n\t}\n\n\t// create selector for plugin\n\t$.expr[ \":\" ][ fullName.toLowerCase() ] = function( elem ) {\n\t\treturn !!$.data( elem, fullName );\n\t};\n\n\t$[ namespace ] = $[ namespace ] || {};\n\texistingConstructor = $[ namespace ][ name ];\n\tconstructor = $[ namespace ][ name ] = function( options, element ) {\n\t\t// allow instantiation without \"new\" keyword\n\t\tif ( !this._createWidget ) {\n\t\t\treturn new constructor( options, element );\n\t\t}\n\n\t\t// allow instantiation without initializing for simple inheritance\n\t\t// must use \"new\" keyword (the code above always passes args)\n\t\tif ( arguments.length ) {\n\t\t\tthis._createWidget( options, element );\n\t\t}\n\t};\n\t// extend with the existing constructor to carry over any static properties\n\t$.extend( constructor, existingConstructor, {\n\t\tversion: prototype.version,\n\t\t// copy the object used to create the prototype in case we need to\n\t\t// redefine the widget later\n\t\t_proto: $.extend( {}, prototype ),\n\t\t// track widgets that inherit from this widget in case this widget is\n\t\t// redefined after a widget inherits from it\n\t\t_childConstructors: []\n\t});\n\n\tbasePrototype = new base();\n\t// we need to make the options hash a property directly on the new instance\n\t// otherwise we'll modify the options hash on the prototype that we're\n\t// inheriting from\n\tbasePrototype.options = $.widget.extend( {}, basePrototype.options );\n\t$.each( prototype, function( prop, value ) {\n\t\tif ( !$.isFunction( value ) ) {\n\t\t\tproxiedPrototype[ prop ] = value;\n\t\t\treturn;\n\t\t}\n\t\tproxiedPrototype[ prop ] = (function() {\n\t\t\tvar _super = function() {\n\t\t\t\t\treturn base.prototype[ prop ].apply( this, arguments );\n\t\t\t\t},\n\t\t\t\t_superApply = function( args ) {\n\t\t\t\t\treturn base.prototype[ prop ].apply( this, args );\n\t\t\t\t};\n\t\t\treturn function() {\n\t\t\t\tvar __super = this._super,\n\t\t\t\t\t__superApply = this._superApply,\n\t\t\t\t\treturnValue;\n\n\t\t\t\tthis._super = _super;\n\t\t\t\tthis._superApply = _superApply;\n\n\t\t\t\treturnValue = value.apply( this, arguments );\n\n\t\t\t\tthis._super = __super;\n\t\t\t\tthis._superApply = __superApply;\n\n\t\t\t\treturn returnValue;\n\t\t\t};\n\t\t})();\n\t});\n\tconstructor.prototype = $.widget.extend( basePrototype, {\n\t\t// TODO: remove support for widgetEventPrefix\n\t\t// always use the name + a colon as the prefix, e.g., draggable:start\n\t\t// don't prefix for widgets that aren't DOM-based\n\t\twidgetEventPrefix: existingConstructor ? basePrototype.widgetEventPrefix : name\n\t}, proxiedPrototype, {\n\t\tconstructor: constructor,\n\t\tnamespace: namespace,\n\t\twidgetName: name,\n\t\twidgetFullName: fullName\n\t});\n\n\t// If this widget is being redefined then we need to find all widgets that\n\t// are inheriting from it and redefine all of them so that they inherit from\n\t// the new version of this widget. We're essentially trying to replace one\n\t// level in the prototype chain.\n\tif ( existingConstructor ) {\n\t\t$.each( existingConstructor._childConstructors, function( i, child ) {\n\t\t\tvar childPrototype = child.prototype;\n\n\t\t\t// redefine the child widget using the same prototype that was\n\t\t\t// originally used, but inherit from the new version of the base\n\t\t\t$.widget( childPrototype.namespace + \".\" + childPrototype.widgetName, constructor, child._proto );\n\t\t});\n\t\t// remove the list of existing child constructors from the old constructor\n\t\t// so the old child constructors can be garbage collected\n\t\tdelete existingConstructor._childConstructors;\n\t} else {\n\t\tbase._childConstructors.push( constructor );\n\t}\n\n\t$.widget.bridge( name, constructor );\n};\n\n$.widget.extend = function( target ) {\n\tvar input = slice.call( arguments, 1 ),\n\t\tinputIndex = 0,\n\t\tinputLength = input.length,\n\t\tkey,\n\t\tvalue;\n\tfor ( ; inputIndex < inputLength; inputIndex++ ) {\n\t\tfor ( key in input[ inputIndex ] ) {\n\t\t\tvalue = input[ inputIndex ][ key ];\n\t\t\tif ( input[ inputIndex ].hasOwnProperty( key ) && value !== undefined ) {\n\t\t\t\t// Clone objects\n\t\t\t\tif ( $.isPlainObject( value ) ) {\n\t\t\t\t\ttarget[ key ] = $.isPlainObject( target[ key ] ) ?\n\t\t\t\t\t\t$.widget.extend( {}, target[ key ], value ) :\n\t\t\t\t\t\t// Don't extend strings, arrays, etc. with objects\n\t\t\t\t\t\t$.widget.extend( {}, value );\n\t\t\t\t// Copy everything else by reference\n\t\t\t\t} else {\n\t\t\t\t\ttarget[ key ] = value;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn target;\n};\n\n$.widget.bridge = function( name, object ) {\n\tvar fullName = object.prototype.widgetFullName || name;\n\t$.fn[ name ] = function( options ) {\n\t\tvar isMethodCall = typeof options === \"string\",\n\t\t\targs = slice.call( arguments, 1 ),\n\t\t\treturnValue = this;\n\n\t\t// allow multiple hashes to be passed on init\n\t\toptions = !isMethodCall && args.length ?\n\t\t\t$.widget.extend.apply( null, [ options ].concat(args) ) :\n\t\t\toptions;\n\n\t\tif ( isMethodCall ) {\n\t\t\tthis.each(function() {\n\t\t\t\tvar methodValue,\n\t\t\t\t\tinstance = $.data( this, fullName );\n\t\t\t\tif ( !instance ) {\n\t\t\t\t\treturn $.error( \"cannot call methods on \" + name + \" prior to initialization; \" +\n\t\t\t\t\t\t\"attempted to call method '\" + options + \"'\" );\n\t\t\t\t}\n\t\t\t\tif ( !$.isFunction( instance[options] ) || options.charAt( 0 ) === \"_\" ) {\n\t\t\t\t\treturn $.error( \"no such method '\" + options + \"' for \" + name + \" widget instance\" );\n\t\t\t\t}\n\t\t\t\tmethodValue = instance[ options ].apply( instance, args );\n\t\t\t\tif ( methodValue !== instance && methodValue !== undefined ) {\n\t\t\t\t\treturnValue = methodValue && methodValue.jquery ?\n\t\t\t\t\t\treturnValue.pushStack( methodValue.get() ) :\n\t\t\t\t\t\tmethodValue;\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t});\n\t\t} else {\n\t\t\tthis.each(function() {\n\t\t\t\tvar instance = $.data( this, fullName );\n\t\t\t\tif ( instance ) {\n\t\t\t\t\tinstance.option( options || {} )._init();\n\t\t\t\t} else {\n\t\t\t\t\t$.data( this, fullName, new object( options, this ) );\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\n\t\treturn returnValue;\n\t};\n};\n\n$.Widget = function( /* options, element */ ) {};\n$.Widget._childConstructors = [];\n\n$.Widget.prototype = {\n\twidgetName: \"widget\",\n\twidgetEventPrefix: \"\",\n\tdefaultElement: \"<div>\",\n\toptions: {\n\t\tdisabled: false,\n\n\t\t// callbacks\n\t\tcreate: null\n\t},\n\t_createWidget: function( options, element ) {\n\t\telement = $( element || this.defaultElement || this )[ 0 ];\n\t\tthis.element = $( element );\n\t\tthis.uuid = uuid++;\n\t\tthis.eventNamespace = \".\" + this.widgetName + this.uuid;\n\t\tthis.options = $.widget.extend( {},\n\t\t\tthis.options,\n\t\t\tthis._getCreateOptions(),\n\t\t\toptions );\n\n\t\tthis.bindings = $();\n\t\tthis.hoverable = $();\n\t\tthis.focusable = $();\n\n\t\tif ( element !== this ) {\n\t\t\t$.data( element, this.widgetFullName, this );\n\t\t\tthis._on( true, this.element, {\n\t\t\t\tremove: function( event ) {\n\t\t\t\t\tif ( event.target === element ) {\n\t\t\t\t\t\tthis.destroy();\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t});\n\t\t\tthis.document = $( element.style ?\n\t\t\t\t// element within the document\n\t\t\t\telement.ownerDocument :\n\t\t\t\t// element is window or document\n\t\t\t\telement.document || element );\n\t\t\tthis.window = $( this.document[0].defaultView || this.document[0].parentWindow );\n\t\t}\n\n\t\tthis._create();\n\t\tthis._trigger( \"create\", null, this._getCreateEventData() );\n\t\tthis._init();\n\t},\n\t_getCreateOptions: $.noop,\n\t_getCreateEventData: $.noop,\n\t_create: $.noop,\n\t_init: $.noop,\n\n\tdestroy: function() {\n\t\tthis._destroy();\n\t\t// we can probably remove the unbind calls in 2.0\n\t\t// all event bindings should go through this._on()\n\t\tthis.element\n\t\t\t.unbind( this.eventNamespace )\n\t\t\t// 1.9 BC for #7810\n\t\t\t// TODO remove dual storage\n\t\t\t.removeData( this.widgetName )\n\t\t\t.removeData( this.widgetFullName )\n\t\t\t// support: jquery <1.6.3\n\t\t\t// http://bugs.jquery.com/ticket/9413\n\t\t\t.removeData( $.camelCase( this.widgetFullName ) );\n\t\tthis.widget()\n\t\t\t.unbind( this.eventNamespace )\n\t\t\t.removeAttr( \"aria-disabled\" )\n\t\t\t.removeClass(\n\t\t\t\tthis.widgetFullName + \"-disabled \" +\n\t\t\t\t\"ui-state-disabled\" );\n\n\t\t// clean up events and states\n\t\tthis.bindings.unbind( this.eventNamespace );\n\t\tthis.hoverable.removeClass( \"ui-state-hover\" );\n\t\tthis.focusable.removeClass( \"ui-state-focus\" );\n\t},\n\t_destroy: $.noop,\n\n\twidget: function() {\n\t\treturn this.element;\n\t},\n\n\toption: function( key, value ) {\n\t\tvar options = key,\n\t\t\tparts,\n\t\t\tcurOption,\n\t\t\ti;\n\n\t\tif ( arguments.length === 0 ) {\n\t\t\t// don't return a reference to the internal hash\n\t\t\treturn $.widget.extend( {}, this.options );\n\t\t}\n\n\t\tif ( typeof key === \"string\" ) {\n\t\t\t// handle nested keys, e.g., \"foo.bar\" => { foo: { bar: ___ } }\n\t\t\toptions = {};\n\t\t\tparts = key.split( \".\" );\n\t\t\tkey = parts.shift();\n\t\t\tif ( parts.length ) {\n\t\t\t\tcurOption = options[ key ] = $.widget.extend( {}, this.options[ key ] );\n\t\t\t\tfor ( i = 0; i < parts.length - 1; i++ ) {\n\t\t\t\t\tcurOption[ parts[ i ] ] = curOption[ parts[ i ] ] || {};\n\t\t\t\t\tcurOption = curOption[ parts[ i ] ];\n\t\t\t\t}\n\t\t\t\tkey = parts.pop();\n\t\t\t\tif ( value === undefined ) {\n\t\t\t\t\treturn curOption[ key ] === undefined ? null : curOption[ key ];\n\t\t\t\t}\n\t\t\t\tcurOption[ key ] = value;\n\t\t\t} else {\n\t\t\t\tif ( value === undefined ) {\n\t\t\t\t\treturn this.options[ key ] === undefined ? null : this.options[ key ];\n\t\t\t\t}\n\t\t\t\toptions[ key ] = value;\n\t\t\t}\n\t\t}\n\n\t\tthis._setOptions( options );\n\n\t\treturn this;\n\t},\n\t_setOptions: function( options ) {\n\t\tvar key;\n\n\t\tfor ( key in options ) {\n\t\t\tthis._setOption( key, options[ key ] );\n\t\t}\n\n\t\treturn this;\n\t},\n\t_setOption: function( key, value ) {\n\t\tthis.options[ key ] = value;\n\n\t\tif ( key === \"disabled\" ) {\n\t\t\tthis.widget()\n\t\t\t\t.toggleClass( this.widgetFullName + \"-disabled ui-state-disabled\", !!value )\n\t\t\t\t.attr( \"aria-disabled\", value );\n\t\t\tthis.hoverable.removeClass( \"ui-state-hover\" );\n\t\t\tthis.focusable.removeClass( \"ui-state-focus\" );\n\t\t}\n\n\t\treturn this;\n\t},\n\n\tenable: function() {\n\t\treturn this._setOption( \"disabled\", false );\n\t},\n\tdisable: function() {\n\t\treturn this._setOption( \"disabled\", true );\n\t},\n\n\t_on: function( suppressDisabledCheck, element, handlers ) {\n\t\tvar delegateElement,\n\t\t\tinstance = this;\n\n\t\t// no suppressDisabledCheck flag, shuffle arguments\n\t\tif ( typeof suppressDisabledCheck !== \"boolean\" ) {\n\t\t\thandlers = element;\n\t\t\telement = suppressDisabledCheck;\n\t\t\tsuppressDisabledCheck = false;\n\t\t}\n\n\t\t// no element argument, shuffle and use this.element\n\t\tif ( !handlers ) {\n\t\t\thandlers = element;\n\t\t\telement = this.element;\n\t\t\tdelegateElement = this.widget();\n\t\t} else {\n\t\t\t// accept selectors, DOM elements\n\t\t\telement = delegateElement = $( element );\n\t\t\tthis.bindings = this.bindings.add( element );\n\t\t}\n\n\t\t$.each( handlers, function( event, handler ) {\n\t\t\tfunction handlerProxy() {\n\t\t\t\t// allow widgets to customize the disabled handling\n\t\t\t\t// - disabled as an array instead of boolean\n\t\t\t\t// - disabled class as method for disabling individual parts\n\t\t\t\tif ( !suppressDisabledCheck &&\n\t\t\t\t\t\t( instance.options.disabled === true ||\n\t\t\t\t\t\t\t$( this ).hasClass( \"ui-state-disabled\" ) ) ) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\treturn ( typeof handler === \"string\" ? instance[ handler ] : handler )\n\t\t\t\t\t.apply( instance, arguments );\n\t\t\t}\n\n\t\t\t// copy the guid so direct unbinding works\n\t\t\tif ( typeof handler !== \"string\" ) {\n\t\t\t\thandlerProxy.guid = handler.guid =\n\t\t\t\t\thandler.guid || handlerProxy.guid || $.guid++;\n\t\t\t}\n\n\t\t\tvar match = event.match( /^(\\w+)\\s*(.*)$/ ),\n\t\t\t\teventName = match[1] + instance.eventNamespace,\n\t\t\t\tselector = match[2];\n\t\t\tif ( selector ) {\n\t\t\t\tdelegateElement.delegate( selector, eventName, handlerProxy );\n\t\t\t} else {\n\t\t\t\telement.bind( eventName, handlerProxy );\n\t\t\t}\n\t\t});\n\t},\n\n\t_off: function( element, eventName ) {\n\t\teventName = (eventName || \"\").split( \" \" ).join( this.eventNamespace + \" \" ) + this.eventNamespace;\n\t\telement.unbind( eventName ).undelegate( eventName );\n\t},\n\n\t_delay: function( handler, delay ) {\n\t\tfunction handlerProxy() {\n\t\t\treturn ( typeof handler === \"string\" ? instance[ handler ] : handler )\n\t\t\t\t.apply( instance, arguments );\n\t\t}\n\t\tvar instance = this;\n\t\treturn setTimeout( handlerProxy, delay || 0 );\n\t},\n\n\t_hoverable: function( element ) {\n\t\tthis.hoverable = this.hoverable.add( element );\n\t\tthis._on( element, {\n\t\t\tmouseenter: function( event ) {\n\t\t\t\t$( event.currentTarget ).addClass( \"ui-state-hover\" );\n\t\t\t},\n\t\t\tmouseleave: function( event ) {\n\t\t\t\t$( event.currentTarget ).removeClass( \"ui-state-hover\" );\n\t\t\t}\n\t\t});\n\t},\n\n\t_focusable: function( element ) {\n\t\tthis.focusable = this.focusable.add( element );\n\t\tthis._on( element, {\n\t\t\tfocusin: function( event ) {\n\t\t\t\t$( event.currentTarget ).addClass( \"ui-state-focus\" );\n\t\t\t},\n\t\t\tfocusout: function( event ) {\n\t\t\t\t$( event.currentTarget ).removeClass( \"ui-state-focus\" );\n\t\t\t}\n\t\t});\n\t},\n\n\t_trigger: function( type, event, data ) {\n\t\tvar prop, orig,\n\t\t\tcallback = this.options[ type ];\n\n\t\tdata = data || {};\n\t\tevent = $.Event( event );\n\t\tevent.type = ( type === this.widgetEventPrefix ?\n\t\t\ttype :\n\t\t\tthis.widgetEventPrefix + type ).toLowerCase();\n\t\t// the original event may come from any element\n\t\t// so we need to reset the target on the new event\n\t\tevent.target = this.element[ 0 ];\n\n\t\t// copy original event properties over to the new event\n\t\torig = event.originalEvent;\n\t\tif ( orig ) {\n\t\t\tfor ( prop in orig ) {\n\t\t\t\tif ( !( prop in event ) ) {\n\t\t\t\t\tevent[ prop ] = orig[ prop ];\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tthis.element.trigger( event, data );\n\t\treturn !( $.isFunction( callback ) &&\n\t\t\tcallback.apply( this.element[0], [ event ].concat( data ) ) === false ||\n\t\t\tevent.isDefaultPrevented() );\n\t}\n};\n\n$.each( { show: \"fadeIn\", hide: \"fadeOut\" }, function( method, defaultEffect ) {\n\t$.Widget.prototype[ \"_\" + method ] = function( element, options, callback ) {\n\t\tif ( typeof options === \"string\" ) {\n\t\t\toptions = { effect: options };\n\t\t}\n\t\tvar hasOptions,\n\t\t\teffectName = !options ?\n\t\t\t\tmethod :\n\t\t\t\toptions === true || typeof options === \"number\" ?\n\t\t\t\t\tdefaultEffect :\n\t\t\t\t\toptions.effect || defaultEffect;\n\t\toptions = options || {};\n\t\tif ( typeof options === \"number\" ) {\n\t\t\toptions = { duration: options };\n\t\t}\n\t\thasOptions = !$.isEmptyObject( options );\n\t\toptions.complete = callback;\n\t\tif ( options.delay ) {\n\t\t\telement.delay( options.delay );\n\t\t}\n\t\tif ( hasOptions && $.effects && $.effects.effect[ effectName ] ) {\n\t\t\telement[ method ]( options );\n\t\t} else if ( effectName !== method && element[ effectName ] ) {\n\t\t\telement[ effectName ]( options.duration, options.easing, callback );\n\t\t} else {\n\t\t\telement.queue(function( next ) {\n\t\t\t\t$( this )[ method ]();\n\t\t\t\tif ( callback ) {\n\t\t\t\t\tcallback.call( element[ 0 ] );\n\t\t\t\t}\n\t\t\t\tnext();\n\t\t\t});\n\t\t}\n\t};\n});\n\n}));\n"
  },
  {
    "path": "samples/photo_album_gae/src/main/webapp/assets/stylesheets/application.css",
    "content": "body { font-family: Helvetica, Arial, sans-serif; color: #333; margin: 10px; width: 960px }\n#posterframe { position: absolute; right: 10px; top: 10px; }\nh1 { color: #0e2953; font-size: 18px; }\nh2 { color: #666; font-size: 16px; }\np { font-size: 14px; line-height: 18px; }\n#logo { height: 51px; width: 241px; }\na { color: #0b63b6 }\n\n.actions { margin: 20px 0; }\n.upload_link { color: #000; border: 1px solid #aaa; background-color: #e0e0e0;\n    font-size: 18px; padding: 5px 10px; width: 250px; margin: 10px 0 20px 0;\n    font-weight: bold; text-align: center; text-decoration: none; margin: 5px; }\n\n.photo { margin: 10px; padding: 10px; border-top: 2px solid #ccc; }\n.photo .thumbnail { margin-top: 10px; display: block; max-width: 200px; border: none; }\n.toggle_info { margin-top: 10px; font-weight: bold; color: #e62401; display: block; }\n.thumbnail_holder { height: 182px; margin-bottom: 5px; margin-right: 10px; }\n.info td, .uploaded_info td { font-size: 12px }\n.note { margin: 20px 0}\n\n.more_info, .show_more_info .less_info { display: none; }\n.show_more_info .more_info, .less_info { display: inline-block; }\n.inline { display: inline-block; }\ntd { vertical-align: top; padding-right: 5px; }\n\n#backend_upload, #direct_upload { padding: 20px 20px; margin: 20px 0;\n    border-top: 1px solid #ccc; border-bottom: 1px solid #ccc; }\n\n#backend_upload h1, #direct_upload h1 { margin: 0 0 15px 0; }\n\n.back_link { font-weight: bold; display: block; font-size: 16px; margin: 10px 0; }\n\nform { border: 1px solid #ddd; margin: 15px 0; padding: 15px 0; border-radius: 4px; }\nform .form_line { margin-bottom: 20px; }\nform .form_controls { margin-left: 180px; }\nform label { float: left; width: 160px; padding-top: 3px; text-align: right; }\nform .error { color: #c55; margin: 0 10px; }\n\n#direct_upload { border: 4px dashed #ccc; }\n\n.upload_details { font-size: 12px; margin: 20px; border-top: 1px solid #ccc; word-wrap: break-word; }\n\n.upload_button_holder {\n    position: relative;\n    display: inline-block;\n    overflow: hidden;\n}\n\n.upload_button_holder .upload_button {\n    display: block;\n    position: relative;\n    font-weight: bold;\n    font-size: 14px;\n    background-color: rgb(15, 97, 172);\n    color: #fff;\n    padding: 5px 0;\n    border: 1px solid #000;\n    border-radius: 4px;\n    width: 100px;\n    height: 18px;\n    text-decoration: none;\n    text-align: center;\n    cursor: pointer;\n}\n\n.upload_button_holder:hover .upload_button {\n    background-color: rgb(17, 133, 240);\n}\n\n.upload_button_holder .cloudinary-fileupload {\n    opacity: 0;\n    filter: alpha(opacity=0);\n    cursor: pointer;\n    position: absolute;\n    top: 0;\n    left: 0;\n    width: 100%;\n    height: 100%;\n    margin: 0;\n    padding: 0;\n    border: none;\n}\n"
  },
  {
    "path": "settings.gradle",
    "content": "rootProject.name = 'cloudinary-parent'\ninclude ':cloudinary-core'\ninclude ':cloudinary-taglib'\ninclude ':cloudinary-http5'\ninclude ':cloudinary-test-common'\n\n"
  },
  {
    "path": "tools/update_version.sh",
    "content": "#!/usr/bin/env bash\nnew_version=$1\n\ncurrent_version=`grep -oP \"(?<=VERSION \\= \\\")([0-9.]+)(?=\\\")\" cloudinary-core/src/main/java/com/cloudinary/Cloudinary.java`\ncurrent_version_re=${current_version//./\\\\.}\necho \"Current version is $current_version\"\nif [ -n \"$new_version\" ]; then\n    echo \"New version will be $new_version\"\n    echo \"Pattern used: $current_version_re\"\n    sed -e \"s/${current_version_re}/${new_version}/g\" -i \"\" cloudinary-core/src/main/java/com/cloudinary/Cloudinary.java\n    sed -e \"s/${current_version_re}/${new_version}/g\" -i \"\" README.md\n    sed -e \"s/${current_version_re}/${new_version}/g\" -i \"\" gradle.properties\n    git changelog -t $new_version\nelse\n    echo \"Usage: $0 <new version>\"\n    echo \"For example: $0 1.9.2\"\nfi\n"
  }
]