From 53ae8969d965012a75c40d36c329f52c1be3b7be Mon Sep 17 00:00:00 2001
From: Andrew Lindesay
Date: Fri, 15 Nov 2013 21:51:45 +1300
Subject: [PATCH] + initial load of the application server project
---
LICENSE.TXT | 3 +
README.TXT | 76 ++++
haikudepotserver-api1/pom.xml | 25 ++
.../haikudepotserver/api1/CaptchaApi.java | 30 ++
.../api1/MiscellaneousApi.java | 31 ++
.../haikuos/haikudepotserver/api1/PkgApi.java | 36 ++
.../haikudepotserver/api1/UserApi.java | 42 +++
.../model/captcha/GenerateCaptchaRequest.java | 9 +
.../model/captcha/GenerateCaptchaResult.java | 23 ++
.../GetAllArchitecturesRequest.java | 9 +
.../GetAllArchitecturesResult.java | 18 +
.../miscellaneous/GetAllMessagesRequest.java | 12 +
.../miscellaneous/GetAllMessagesResult.java | 18 +
.../api1/model/pkg/GetPkgRequest.java | 36 ++
.../api1/model/pkg/GetPkgResult.java | 46 +++
.../api1/model/pkg/SearchPkgsRequest.java | 29 ++
.../api1/model/pkg/SearchPkgsResult.java | 29 ++
.../model/user/AuthenticateUserRequest.java | 13 +
.../model/user/AuthenticateUserResult.java | 12 +
.../api1/model/user/CreateUserRequest.java | 29 ++
.../api1/model/user/CreateUserResult.java | 9 +
.../api1/model/user/GetUserRequest.java | 12 +
.../api1/model/user/GetUserResult.java | 12 +
.../support/CaptchaBadResponseException.java | 18 +
.../api1/support/Constants.java | 14 +
.../api1/support/ObjectNotFoundException.java | 45 +++
.../api1/support/ValidationException.java | 49 +++
.../api1/support/ValidationFailure.java | 46 +++
haikudepotserver-packagefile/pom.xml | 53 +++
.../org/haikuos/pkg/AttributeContext.java | 37 ++
.../org/haikuos/pkg/AttributeIterator.java | 315 ++++++++++++++++
.../main/java/org/haikuos/pkg/FileHelper.java | 84 +++++
.../java/org/haikuos/pkg/HpkException.java | 23 ++
.../java/org/haikuos/pkg/HpkStringTable.java | 112 ++++++
.../org/haikuos/pkg/HpkrFileExtractor.java | 152 ++++++++
.../main/java/org/haikuos/pkg/HpkrHeader.java | 128 +++++++
.../java/org/haikuos/pkg/PkgException.java | 18 +
.../main/java/org/haikuos/pkg/PkgFactory.java | 158 ++++++++
.../java/org/haikuos/pkg/PkgIterator.java | 59 +++
.../java/org/haikuos/pkg/StringTable.java | 22 ++
.../org/haikuos/pkg/heap/HeapCompression.java | 11 +
.../org/haikuos/pkg/heap/HeapCoordinates.java | 66 ++++
.../java/org/haikuos/pkg/heap/HeapReader.java | 30 ++
.../org/haikuos/pkg/heap/HpkHeapReader.java | 347 ++++++++++++++++++
.../java/org/haikuos/pkg/model/Attribute.java | 94 +++++
.../org/haikuos/pkg/model/AttributeId.java | 99 +++++
.../org/haikuos/pkg/model/AttributeType.java | 12 +
.../org/haikuos/pkg/model/IntAttribute.java | 61 +++
.../main/java/org/haikuos/pkg/model/Pkg.java | 127 +++++++
.../haikuos/pkg/model/PkgArchitecture.java | 13 +
.../java/org/haikuos/pkg/model/PkgUrl.java | 39 ++
.../org/haikuos/pkg/model/PkgUrlType.java | 10 +
.../org/haikuos/pkg/model/PkgVersion.java | 69 ++++
.../org/haikuos/pkg/model/RawAttribute.java | 14 +
.../haikuos/pkg/model/RawHeapAttribute.java | 62 ++++
.../haikuos/pkg/model/RawInlineAttribute.java | 57 +++
.../haikuos/pkg/model/StringAttribute.java | 14 +
.../pkg/model/StringInlineAttribute.java | 59 +++
.../pkg/model/StringTableRefAttribute.java | 59 +++
.../haikuos/pkg/output/AttributeWriter.java | 99 +++++
.../org/haikuos/pkg/output/PkgWriter.java | 47 +++
.../java/org/haikuos/pkg/package-info.java | 11 +
.../haikuos/pkg/tool/AttributeDumpTool.java | 70 ++++
.../org/haikuos/pkg/tool/PkgDumpTool.java | 73 ++++
.../org/haikuos/pkg/AbstractHpkrTest.java | 54 +++
.../pkg/HpkrFileExtractorAttributeTest.java | 82 +++++
.../java/org/haikuos/pkg/PkgFactoryTest.java | 117 ++++++
.../src/test/resources/README.TXT | 5 +
.../src/test/resources/repo.hpkr | Bin 0 -> 48997 bytes
haikudepotserver-parent/pom.xml | 157 ++++++++
haikudepotserver-webapp/pom.xml | 174 +++++++++
.../src/etc/ant/fetchwebresources-build.xml | 57 +++
.../haikudepotserver/api1/CaptchaApiImpl.java | 35 ++
.../api1/MiscellaneousApiImpl.java | 106 ++++++
.../haikudepotserver/api1/PkgApiImpl.java | 232 ++++++++++++
.../haikudepotserver/api1/UserApiImpl.java | 141 +++++++
.../api1/support/ErrorResolverImpl.java | 120 ++++++
.../api1/support/ObjectMapperFactory.java | 37 ++
.../captcha/CaptchaService.java | 83 +++++
.../captcha/DatabaseCaptchaRepository.java | 171 +++++++++
.../SimpleMathProblemCaptchaAlgorithm.java | 95 +++++
.../captcha/model/Captcha.java | 71 ++++
.../captcha/model/CaptchaAlgorithm.java | 16 +
.../captcha/model/CaptchaRepository.java | 38 ++
.../controller/EntryPointController.java | 34 ++
.../ImportRepositoryDataController.java | 76 ++++
.../WebResourceGroupController.java | 118 ++++++
.../haikudepotserver/model/Architecture.java | 41 +++
.../haikudepotserver/model/HaikuDepot.java | 24 ++
.../haikuos/haikudepotserver/model/Pkg.java | 45 +++
.../haikudepotserver/model/PkgUrlType.java | 28 ++
.../haikudepotserver/model/PkgVersion.java | 111 ++++++
.../model/PkgVersionCopyright.java | 12 +
.../model/PkgVersionLicense.java | 12 +
.../model/PkgVersionLocalization.java | 12 +
.../haikudepotserver/model/PkgVersionUrl.java | 32 ++
.../haikudepotserver/model/Publisher.java | 14 +
.../haikudepotserver/model/Repository.java | 49 +++
.../haikuos/haikudepotserver/model/User.java | 96 +++++
.../model/auto/_Architecture.java | 21 ++
.../model/auto/_HaikuDepot.java | 12 +
.../haikudepotserver/model/auto/_Pkg.java | 61 +++
.../model/auto/_PkgUrlType.java | 21 ++
.../model/auto/_PkgVersion.java | 172 +++++++++
.../model/auto/_PkgVersionCopyright.java | 35 ++
.../model/auto/_PkgVersionLicense.java | 35 ++
.../model/auto/_PkgVersionLocalization.java | 43 +++
.../model/auto/_PkgVersionUrl.java | 46 +++
.../model/auto/_Publisher.java | 89 +++++
.../model/auto/_Repository.java | 69 ++++
.../haikudepotserver/model/auto/_User.java | 64 ++++
.../haikudepotserver/model/package-info.java | 8 +
.../model/support/AbstractDataObject.java | 43 +++
.../haikudepotserver/model/support/Coded.java | 18 +
.../support/CreateAndModifyTimestamped.java | 23 ++
.../services/ImportPackageService.java | 157 ++++++++
.../services/ImportRepositoryDataService.java | 194 ++++++++++
.../services/SearchPkgsService.java | 138 +++++++
.../model/ImportRepositoryDataJob.java | 56 +++
.../model/SearchPkgsSpecification.java | 72 ++++
.../haikudepotserver/support/Closeables.java | 67 ++++
.../cayenne/ConfigureDataSourceModule.java | 51 +++
.../support/cayenne/LikeHelper.java | 16 +
...stAddCreateAndModifyTimestampListener.java | 31 ++
.../FlywayMigrationOrchestration.java | 73 ++++
.../support/web/WebResourceGroupTag.java | 106 ++++++
.../support/web/model/WebResourceGroup.java | 67 ++++
.../src/main/resources/HaikuDepot.map.xml | 284 ++++++++++++++
.../resources/cayenne-haikudepotserver.xml | 17 +
.../db/captcha/migration/V1.0__Initialize.sql | 12 +
.../haikudepot/migration/V1.0__Initialize.sql | 84 +++++
.../main/resources/local-sample.properties | 15 +
.../src/main/resources/logback.xml | 22 ++
.../src/main/resources/messages.properties | 12 +
.../resources/spring/application-context.xml | 41 +++
.../resources/spring/persistence-context.xml | 47 +++
.../main/resources/spring/servlet-context.xml | 47 +++
.../spring/webresourcegroup-context.xml | 83 +++++
.../main/webapp/WEB-INF/haikudepotserver.tld | 20 +
.../main/webapp/WEB-INF/views/entryPoint.jsp | 33 ++
.../src/main/webapp/WEB-INF/web.xml | 30 ++
.../src/main/webapp/bootstrap/README.TXT | 3 +
.../src/main/webapp/css/createuser.css | 4 +
.../src/main/webapp/css/haikudepotserver.css | 42 +++
.../src/main/webapp/css/home.css | 47 +++
.../src/main/webapp/img/favicon.png | Bin 0 -> 48524 bytes
.../src/main/webapp/img/spinner.svg | 21 ++
.../src/main/webapp/js/app/constants.js | 28 ++
.../js/app/controller/authenticateuser.html | 54 +++
.../controller/authenticateusercontroller.js | 99 +++++
.../webapp/js/app/controller/createuser.html | 70 ++++
.../js/app/controller/createusercontroller.js | 149 ++++++++
.../main/webapp/js/app/controller/error.html | 9 +
.../js/app/controller/errorcontroller.js | 15 +
.../main/webapp/js/app/controller/home.html | 70 ++++
.../js/app/controller/homecontroller.js | 205 +++++++++++
.../webapp/js/app/controller/viewpkg.html | 25 ++
.../js/app/controller/viewpkgcontroller.js | 78 ++++
.../webapp/js/app/controller/viewuser.html | 12 +
.../js/app/controller/viewusercontroller.js | 52 +++
.../main/webapp/js/app/directive/banner.html | 53 +++
.../js/app/directive/bannerdirective.js | 117 ++++++
.../webapp/js/app/directive/breadcrumbs.html | 7 +
.../js/app/directive/breadcrumbsdirective.js | 71 ++++
.../js/app/directive/errormessages.html | 3 +
.../app/directive/errormessagesdirective.js | 28 ++
.../js/app/directive/messagedirective.js | 47 +++
.../main/webapp/js/app/directive/spinner.html | 5 +
.../js/app/directive/spinnerdirective.js | 22 ++
.../webapp/js/app/directive/versionlabel.html | 1 +
.../js/app/directive/versionlabeldirective.js | 26 ++
.../main/webapp/js/app/haikudepotserver.js | 12 +
.../src/main/webapp/js/app/routes.js | 20 +
.../webapp/js/app/service/jsonrpcservice.js | 154 ++++++++
.../js/app/service/messagesourceservice.js | 52 +++
.../js/app/service/referencedataservice.js | 86 +++++
.../webapp/js/app/service/userstateservice.js | 100 +++++
.../src/main/webapp/js/lib/README.TXT | 4 +
pom.xml | 16 +
179 files changed, 10272 insertions(+)
create mode 100644 LICENSE.TXT
create mode 100644 README.TXT
create mode 100644 haikudepotserver-api1/pom.xml
create mode 100644 haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/CaptchaApi.java
create mode 100644 haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/MiscellaneousApi.java
create mode 100644 haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/PkgApi.java
create mode 100644 haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/UserApi.java
create mode 100644 haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/captcha/GenerateCaptchaRequest.java
create mode 100644 haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/captcha/GenerateCaptchaResult.java
create mode 100644 haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/miscellaneous/GetAllArchitecturesRequest.java
create mode 100644 haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/miscellaneous/GetAllArchitecturesResult.java
create mode 100644 haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/miscellaneous/GetAllMessagesRequest.java
create mode 100644 haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/miscellaneous/GetAllMessagesResult.java
create mode 100644 haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/pkg/GetPkgRequest.java
create mode 100644 haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/pkg/GetPkgResult.java
create mode 100644 haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/pkg/SearchPkgsRequest.java
create mode 100644 haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/pkg/SearchPkgsResult.java
create mode 100644 haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/user/AuthenticateUserRequest.java
create mode 100644 haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/user/AuthenticateUserResult.java
create mode 100644 haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/user/CreateUserRequest.java
create mode 100644 haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/user/CreateUserResult.java
create mode 100644 haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/user/GetUserRequest.java
create mode 100644 haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/user/GetUserResult.java
create mode 100644 haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/support/CaptchaBadResponseException.java
create mode 100644 haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/support/Constants.java
create mode 100644 haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/support/ObjectNotFoundException.java
create mode 100644 haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/support/ValidationException.java
create mode 100644 haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/support/ValidationFailure.java
create mode 100644 haikudepotserver-packagefile/pom.xml
create mode 100644 haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/AttributeContext.java
create mode 100644 haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/AttributeIterator.java
create mode 100644 haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/FileHelper.java
create mode 100644 haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/HpkException.java
create mode 100644 haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/HpkStringTable.java
create mode 100644 haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/HpkrFileExtractor.java
create mode 100644 haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/HpkrHeader.java
create mode 100644 haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/PkgException.java
create mode 100644 haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/PkgFactory.java
create mode 100644 haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/PkgIterator.java
create mode 100644 haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/StringTable.java
create mode 100644 haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/heap/HeapCompression.java
create mode 100644 haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/heap/HeapCoordinates.java
create mode 100644 haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/heap/HeapReader.java
create mode 100644 haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/heap/HpkHeapReader.java
create mode 100644 haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/model/Attribute.java
create mode 100644 haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/model/AttributeId.java
create mode 100644 haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/model/AttributeType.java
create mode 100644 haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/model/IntAttribute.java
create mode 100644 haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/model/Pkg.java
create mode 100644 haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/model/PkgArchitecture.java
create mode 100644 haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/model/PkgUrl.java
create mode 100644 haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/model/PkgUrlType.java
create mode 100644 haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/model/PkgVersion.java
create mode 100644 haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/model/RawAttribute.java
create mode 100644 haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/model/RawHeapAttribute.java
create mode 100644 haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/model/RawInlineAttribute.java
create mode 100644 haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/model/StringAttribute.java
create mode 100644 haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/model/StringInlineAttribute.java
create mode 100644 haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/model/StringTableRefAttribute.java
create mode 100644 haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/output/AttributeWriter.java
create mode 100644 haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/output/PkgWriter.java
create mode 100644 haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/package-info.java
create mode 100644 haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/tool/AttributeDumpTool.java
create mode 100644 haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/tool/PkgDumpTool.java
create mode 100644 haikudepotserver-packagefile/src/test/java/org/haikuos/pkg/AbstractHpkrTest.java
create mode 100644 haikudepotserver-packagefile/src/test/java/org/haikuos/pkg/HpkrFileExtractorAttributeTest.java
create mode 100644 haikudepotserver-packagefile/src/test/java/org/haikuos/pkg/PkgFactoryTest.java
create mode 100644 haikudepotserver-packagefile/src/test/resources/README.TXT
create mode 100644 haikudepotserver-packagefile/src/test/resources/repo.hpkr
create mode 100644 haikudepotserver-parent/pom.xml
create mode 100644 haikudepotserver-webapp/pom.xml
create mode 100644 haikudepotserver-webapp/src/etc/ant/fetchwebresources-build.xml
create mode 100644 haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/api1/CaptchaApiImpl.java
create mode 100644 haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/api1/MiscellaneousApiImpl.java
create mode 100644 haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/api1/PkgApiImpl.java
create mode 100644 haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/api1/UserApiImpl.java
create mode 100644 haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/api1/support/ErrorResolverImpl.java
create mode 100644 haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/api1/support/ObjectMapperFactory.java
create mode 100644 haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/captcha/CaptchaService.java
create mode 100644 haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/captcha/DatabaseCaptchaRepository.java
create mode 100644 haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/captcha/SimpleMathProblemCaptchaAlgorithm.java
create mode 100644 haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/captcha/model/Captcha.java
create mode 100644 haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/captcha/model/CaptchaAlgorithm.java
create mode 100644 haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/captcha/model/CaptchaRepository.java
create mode 100644 haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/controller/EntryPointController.java
create mode 100644 haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/controller/ImportRepositoryDataController.java
create mode 100644 haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/controller/WebResourceGroupController.java
create mode 100644 haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/model/Architecture.java
create mode 100644 haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/model/HaikuDepot.java
create mode 100644 haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/model/Pkg.java
create mode 100644 haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/model/PkgUrlType.java
create mode 100644 haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/model/PkgVersion.java
create mode 100644 haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/model/PkgVersionCopyright.java
create mode 100644 haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/model/PkgVersionLicense.java
create mode 100644 haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/model/PkgVersionLocalization.java
create mode 100644 haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/model/PkgVersionUrl.java
create mode 100644 haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/model/Publisher.java
create mode 100644 haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/model/Repository.java
create mode 100644 haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/model/User.java
create mode 100644 haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/model/auto/_Architecture.java
create mode 100644 haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/model/auto/_HaikuDepot.java
create mode 100644 haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/model/auto/_Pkg.java
create mode 100644 haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/model/auto/_PkgUrlType.java
create mode 100644 haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/model/auto/_PkgVersion.java
create mode 100644 haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/model/auto/_PkgVersionCopyright.java
create mode 100644 haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/model/auto/_PkgVersionLicense.java
create mode 100644 haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/model/auto/_PkgVersionLocalization.java
create mode 100644 haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/model/auto/_PkgVersionUrl.java
create mode 100644 haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/model/auto/_Publisher.java
create mode 100644 haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/model/auto/_Repository.java
create mode 100644 haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/model/auto/_User.java
create mode 100644 haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/model/package-info.java
create mode 100644 haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/model/support/AbstractDataObject.java
create mode 100644 haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/model/support/Coded.java
create mode 100644 haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/model/support/CreateAndModifyTimestamped.java
create mode 100644 haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/services/ImportPackageService.java
create mode 100644 haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/services/ImportRepositoryDataService.java
create mode 100644 haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/services/SearchPkgsService.java
create mode 100644 haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/services/model/ImportRepositoryDataJob.java
create mode 100644 haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/services/model/SearchPkgsSpecification.java
create mode 100644 haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/support/Closeables.java
create mode 100644 haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/support/cayenne/ConfigureDataSourceModule.java
create mode 100644 haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/support/cayenne/LikeHelper.java
create mode 100644 haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/support/cayenne/PostAddCreateAndModifyTimestampListener.java
create mode 100644 haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/support/db/migration/FlywayMigrationOrchestration.java
create mode 100644 haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/support/web/WebResourceGroupTag.java
create mode 100644 haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/support/web/model/WebResourceGroup.java
create mode 100644 haikudepotserver-webapp/src/main/resources/HaikuDepot.map.xml
create mode 100644 haikudepotserver-webapp/src/main/resources/cayenne-haikudepotserver.xml
create mode 100644 haikudepotserver-webapp/src/main/resources/db/captcha/migration/V1.0__Initialize.sql
create mode 100644 haikudepotserver-webapp/src/main/resources/db/haikudepot/migration/V1.0__Initialize.sql
create mode 100644 haikudepotserver-webapp/src/main/resources/local-sample.properties
create mode 100644 haikudepotserver-webapp/src/main/resources/logback.xml
create mode 100644 haikudepotserver-webapp/src/main/resources/messages.properties
create mode 100644 haikudepotserver-webapp/src/main/resources/spring/application-context.xml
create mode 100644 haikudepotserver-webapp/src/main/resources/spring/persistence-context.xml
create mode 100644 haikudepotserver-webapp/src/main/resources/spring/servlet-context.xml
create mode 100644 haikudepotserver-webapp/src/main/resources/spring/webresourcegroup-context.xml
create mode 100644 haikudepotserver-webapp/src/main/webapp/WEB-INF/haikudepotserver.tld
create mode 100644 haikudepotserver-webapp/src/main/webapp/WEB-INF/views/entryPoint.jsp
create mode 100644 haikudepotserver-webapp/src/main/webapp/WEB-INF/web.xml
create mode 100644 haikudepotserver-webapp/src/main/webapp/bootstrap/README.TXT
create mode 100644 haikudepotserver-webapp/src/main/webapp/css/createuser.css
create mode 100644 haikudepotserver-webapp/src/main/webapp/css/haikudepotserver.css
create mode 100644 haikudepotserver-webapp/src/main/webapp/css/home.css
create mode 100644 haikudepotserver-webapp/src/main/webapp/img/favicon.png
create mode 100644 haikudepotserver-webapp/src/main/webapp/img/spinner.svg
create mode 100644 haikudepotserver-webapp/src/main/webapp/js/app/constants.js
create mode 100644 haikudepotserver-webapp/src/main/webapp/js/app/controller/authenticateuser.html
create mode 100644 haikudepotserver-webapp/src/main/webapp/js/app/controller/authenticateusercontroller.js
create mode 100644 haikudepotserver-webapp/src/main/webapp/js/app/controller/createuser.html
create mode 100644 haikudepotserver-webapp/src/main/webapp/js/app/controller/createusercontroller.js
create mode 100644 haikudepotserver-webapp/src/main/webapp/js/app/controller/error.html
create mode 100644 haikudepotserver-webapp/src/main/webapp/js/app/controller/errorcontroller.js
create mode 100644 haikudepotserver-webapp/src/main/webapp/js/app/controller/home.html
create mode 100644 haikudepotserver-webapp/src/main/webapp/js/app/controller/homecontroller.js
create mode 100644 haikudepotserver-webapp/src/main/webapp/js/app/controller/viewpkg.html
create mode 100644 haikudepotserver-webapp/src/main/webapp/js/app/controller/viewpkgcontroller.js
create mode 100644 haikudepotserver-webapp/src/main/webapp/js/app/controller/viewuser.html
create mode 100644 haikudepotserver-webapp/src/main/webapp/js/app/controller/viewusercontroller.js
create mode 100644 haikudepotserver-webapp/src/main/webapp/js/app/directive/banner.html
create mode 100644 haikudepotserver-webapp/src/main/webapp/js/app/directive/bannerdirective.js
create mode 100644 haikudepotserver-webapp/src/main/webapp/js/app/directive/breadcrumbs.html
create mode 100644 haikudepotserver-webapp/src/main/webapp/js/app/directive/breadcrumbsdirective.js
create mode 100644 haikudepotserver-webapp/src/main/webapp/js/app/directive/errormessages.html
create mode 100644 haikudepotserver-webapp/src/main/webapp/js/app/directive/errormessagesdirective.js
create mode 100644 haikudepotserver-webapp/src/main/webapp/js/app/directive/messagedirective.js
create mode 100644 haikudepotserver-webapp/src/main/webapp/js/app/directive/spinner.html
create mode 100644 haikudepotserver-webapp/src/main/webapp/js/app/directive/spinnerdirective.js
create mode 100644 haikudepotserver-webapp/src/main/webapp/js/app/directive/versionlabel.html
create mode 100644 haikudepotserver-webapp/src/main/webapp/js/app/directive/versionlabeldirective.js
create mode 100644 haikudepotserver-webapp/src/main/webapp/js/app/haikudepotserver.js
create mode 100644 haikudepotserver-webapp/src/main/webapp/js/app/routes.js
create mode 100644 haikudepotserver-webapp/src/main/webapp/js/app/service/jsonrpcservice.js
create mode 100644 haikudepotserver-webapp/src/main/webapp/js/app/service/messagesourceservice.js
create mode 100644 haikudepotserver-webapp/src/main/webapp/js/app/service/referencedataservice.js
create mode 100644 haikudepotserver-webapp/src/main/webapp/js/app/service/userstateservice.js
create mode 100644 haikudepotserver-webapp/src/main/webapp/js/lib/README.TXT
create mode 100644 pom.xml
diff --git a/LICENSE.TXT b/LICENSE.TXT
new file mode 100644
index 00000000..5e1186fb
--- /dev/null
+++ b/LICENSE.TXT
@@ -0,0 +1,3 @@
+These software source code resources are distributed under the
+terms of the MIT License.
+http://opensource.org/licenses/MIT
diff --git a/README.TXT b/README.TXT
new file mode 100644
index 00000000..a04effff
--- /dev/null
+++ b/README.TXT
@@ -0,0 +1,76 @@
+Haiku Depot Server
+~~~~~~~~~~~~~~~~~~
+
+This collection of files represents the source code for the "Haiku Depot Server"; a web-services and HTML environment for working with Haiku packages. This is a maven java project that primarily builds an application server.
+
+---
+REQUIREMENTS, BUILD AND SETUP
+
+To build and use this software, you will require;
+
+* Java JDK 1.6 or better
+* Apache Maven 3.0.3 or better
+* A Postgres 9.1 or better database server
+* An internet connection
+
+On a debian 7 host, the following packages can be installed;
+
+ apt-get install default-jdk
+ apt-get install maven
+ apt-get install postgresql postgresql-client
+
+The project consists of a number of modules. The "haikudepotserver-webapp" is the application server module. This module requires a database in order to function. It is expecting to work with a Postgres database server. Create a blank postgres database and ensure that you are able to access the database over an internet socket authenticating as some user. You can leave the database devoid of schema objects for now because the application server will populate necessary schema objects on the first launch.
+
+The following file in the "haikudepotserver-webapp" is a template configuration file for the application server;
+
+ src/main/resources/local-sample.properties
+
+Copy this to an adjacent file in the same directory called "local.properties". You will need to edit properties starting with "jdbc..." in order to let the application server know how to access your postgres database.
+
+The first build will take longer than 'normal' because it will need to download a number of dependencies from the internet in order to undertake the build. Some downloads are related to web-resources. These downloads are only indirectly managed by the maven build process. For this reason, your first step should be to complete a command-line build by issuing the following command in the same directory as this file;
+
+ mvn package
+
+This step will ensure that the web-resources are populated. You should now be able to either continue to use the project in the command line environment or you can switch to use a java IDE.
+
+To start-up the application server for development purposes, issue the following command from the same top level of the project; the same directory as this file.
+
+ mvn org.apache.tomcat.maven:tomcat7-maven-plugin:2.1:run
+
+This may take some time to start-up; especially the first time. Once it has started-up, it should be possible to connect to the application server using the following URL;
+
+ http://localhost:8080/
+
+There won't be any repositories or data loaded, and because of this, it is not possible to view any data. Now a repository can be added to obtain packages from. Open a SQL terminal and add a repository;
+
+ INSERT INTO
+ haikudepot.repository (
+ id, active, create_timestamp, modify_timestamp,
+ architecture_id, code, url)
+ VALUES (
+ nextval('haikudepot.repository_seq'), true, now(), now(),
+ (SELECT id FROM haikudepot.architecture WHERE code='x86'), 'test', 'file:///tmp/repo.hpkr');
+
+This artificial repository will obtain a file from the local file system's temporary directory. You could, for example, take the test HPKR file supplied in the test resources of the "haikudepotserver-packagefile" module and place this at /tmp/repo.hpkr. Now it should be possible to prompt the system to take-up the repository data by dispatching a URL of this form using a tool such as curl;
+
+ curl "http://localhost:8080/importrepositorydata?code=test"
+
+You should now refresh your browser and it ought to be possible to view the packages that have been imported from the test file.
+
+---
+API
+
+The API for communicating with the server is described in the "haikudepotserver-api1" module. This contains DTO model objects describing the objects to be used in API calls as well as interfaces that describe the API calls that are available. The application server vends the API as JSON-RPC. More information about JSON-RPC can be found here;
+
+ http://www.jsonrpc.org/
+
+This API is intended to be used for the single-page web application as well as a desktop application.
+
+---
+HPKR HANDLING
+
+Haiku packages are described using HPK* files and these are described here;
+
+ http://dev.haiku-os.org/wiki/PackageManagement
+
+The "haikudepotserver-packagefile" module contains handling for HPKR files. Given the requirements of the application server this handling is limited to read-only access.
diff --git a/haikudepotserver-api1/pom.xml b/haikudepotserver-api1/pom.xml
new file mode 100644
index 00000000..2bec96ed
--- /dev/null
+++ b/haikudepotserver-api1/pom.xml
@@ -0,0 +1,25 @@
+
+
+
+ haikudepotserver-parent
+ org.haikuos
+ ../haikudepotserver-parent
+ 1.0.1-SNAPSHOT
+
+
+ 4.0.0
+ org.haikuos
+ haikudepotserver-api1
+ jar
+
+
+
+ com.googlecode
+ jsonrpc4j
+
+
+
+
diff --git a/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/CaptchaApi.java b/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/CaptchaApi.java
new file mode 100644
index 00000000..cc06b917
--- /dev/null
+++ b/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/CaptchaApi.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.api1;
+
+import com.googlecode.jsonrpc4j.JsonRpcService;
+import org.haikuos.haikudepotserver.api1.model.captcha.GenerateCaptchaRequest;
+import org.haikuos.haikudepotserver.api1.model.captcha.GenerateCaptchaResult;
+
+/**
+ * This API is to do with captchas. A captcha is a small image that is shown to a user in order for the user to
+ * supply some textual response from the image in order to verify that the operator is likely to be human and not a
+ * computer. This helps to prevent machine-hacking of systems. This API is able to provide a captcha and other
+ * APIs require that a 'captcha response' is supplied as part of a request. In general a captcha is valid for a
+ * certain length of time.
+ */
+
+@JsonRpcService("/api/v1/captcha")
+public interface CaptchaApi {
+
+ /**
+ * This method will return a captcha that can be used in systems where a captcha response (generated by a
+ * human) is required to be supplied with an API request.
+ */
+
+ GenerateCaptchaResult generateCaptcha(GenerateCaptchaRequest generateCaptchaRequest);
+
+}
diff --git a/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/MiscellaneousApi.java b/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/MiscellaneousApi.java
new file mode 100644
index 00000000..f4c73430
--- /dev/null
+++ b/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/MiscellaneousApi.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.api1;
+
+import com.googlecode.jsonrpc4j.JsonRpcService;
+import org.haikuos.haikudepotserver.api1.model.miscellaneous.GetAllArchitecturesRequest;
+import org.haikuos.haikudepotserver.api1.model.miscellaneous.GetAllArchitecturesResult;
+import org.haikuos.haikudepotserver.api1.model.miscellaneous.GetAllMessagesRequest;
+import org.haikuos.haikudepotserver.api1.model.miscellaneous.GetAllMessagesResult;
+
+@JsonRpcService("/api/v1/miscellaneous")
+public interface MiscellaneousApi {
+
+ /**
+ * This method will return all of the localization messages that might be able to be displayed
+ * to the user from the result of validation problems and so on.
+ */
+
+ GetAllMessagesResult getAllMessages(GetAllMessagesRequest getAllMessagesRequest);
+
+ /**
+ * This method will return a list of all of the possible architectures in the system such as x86 or arm.
+ * Note that this will explicitly exclude the pseudo-architectures of "source" and "any".
+ */
+
+ GetAllArchitecturesResult getAllArchitectures(GetAllArchitecturesRequest getAllArchitecturesRequest);
+
+}
diff --git a/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/PkgApi.java b/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/PkgApi.java
new file mode 100644
index 00000000..f23b2731
--- /dev/null
+++ b/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/PkgApi.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.api1;
+
+import com.googlecode.jsonrpc4j.JsonRpcService;
+import org.haikuos.haikudepotserver.api1.model.pkg.GetPkgRequest;
+import org.haikuos.haikudepotserver.api1.model.pkg.GetPkgResult;
+import org.haikuos.haikudepotserver.api1.model.pkg.SearchPkgsRequest;
+import org.haikuos.haikudepotserver.api1.model.pkg.SearchPkgsResult;
+import org.haikuos.haikudepotserver.api1.support.ObjectNotFoundException;
+
+/**
+ * This API is for access to packages and package versions.
+ */
+
+@JsonRpcService("/api/v1/pkg")
+public interface PkgApi {
+
+ /**
+ * This method can be invoked to get a list of all of the packages that match some search critera in the
+ * request.
+ */
+
+ SearchPkgsResult searchPkgs(SearchPkgsRequest request);
+
+ /**
+ * This method will return a package and the specified versions. It will throw an
+ * {@link org.haikuos.haikudepotserver.api1.support.ObjectNotFoundException} if the package was not able to be located.
+ */
+
+ GetPkgResult getPkg(GetPkgRequest request) throws ObjectNotFoundException;
+
+}
diff --git a/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/UserApi.java b/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/UserApi.java
new file mode 100644
index 00000000..9e3e24f8
--- /dev/null
+++ b/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/UserApi.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.api1;
+
+import com.googlecode.jsonrpc4j.JsonRpcService;
+import org.haikuos.haikudepotserver.api1.model.user.*;
+import org.haikuos.haikudepotserver.api1.support.ObjectNotFoundException;
+
+/**
+ * This interface defines operations that can be undertaken around users.
+ */
+
+@JsonRpcService("/api/v1/user")
+public interface UserApi {
+
+ /**
+ * This method will create a user in the system. It is identified by a username
+ * and authenticated by a password. The password is supplied in the clear.
+ */
+
+ CreateUserResult createUser(CreateUserRequest createUserRequest);
+
+ /**
+ * This method will get the user identified by the nickname in the request object.
+ * If no user was able to be found an instance of {@link org.haikuos.haikudepotserver.api1.support.ObjectNotFoundException}
+ * is thrown.
+ */
+
+ GetUserResult getUser(GetUserRequest getUserRequest) throws ObjectNotFoundException;
+
+ /**
+ * This method will allow a client to authenticate against the server. If this is
+ * successful then the client will know that it is OK to use the authentication
+ * principal and credentials for further API calls.
+ */
+
+ AuthenticateUserResult authenticateUser(AuthenticateUserRequest authenticateUserRequest);
+
+}
diff --git a/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/captcha/GenerateCaptchaRequest.java b/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/captcha/GenerateCaptchaRequest.java
new file mode 100644
index 00000000..2ad01969
--- /dev/null
+++ b/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/captcha/GenerateCaptchaRequest.java
@@ -0,0 +1,9 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.api1.model.captcha;
+
+public class GenerateCaptchaRequest {
+}
diff --git a/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/captcha/GenerateCaptchaResult.java b/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/captcha/GenerateCaptchaResult.java
new file mode 100644
index 00000000..818037af
--- /dev/null
+++ b/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/captcha/GenerateCaptchaResult.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.api1.model.captcha;
+
+public class GenerateCaptchaResult {
+
+ /**
+ * This token uniquely identifies the captcha.
+ */
+
+ public String token;
+
+ /**
+ * This is a base-64 encoded image of the captcha. It could, for example, be used with a data url to render
+ * the image in an "img" tag on a web page.
+ */
+
+ public String pngImageDataBase64;
+
+}
diff --git a/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/miscellaneous/GetAllArchitecturesRequest.java b/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/miscellaneous/GetAllArchitecturesRequest.java
new file mode 100644
index 00000000..5d62daae
--- /dev/null
+++ b/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/miscellaneous/GetAllArchitecturesRequest.java
@@ -0,0 +1,9 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.api1.model.miscellaneous;
+
+public class GetAllArchitecturesRequest {
+}
diff --git a/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/miscellaneous/GetAllArchitecturesResult.java b/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/miscellaneous/GetAllArchitecturesResult.java
new file mode 100644
index 00000000..39a2beeb
--- /dev/null
+++ b/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/miscellaneous/GetAllArchitecturesResult.java
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.api1.model.miscellaneous;
+
+import java.util.List;
+
+public class GetAllArchitecturesResult {
+
+ public List architectures;
+
+ public static class Architecture {
+ public String code;
+ }
+
+}
diff --git a/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/miscellaneous/GetAllMessagesRequest.java b/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/miscellaneous/GetAllMessagesRequest.java
new file mode 100644
index 00000000..51729838
--- /dev/null
+++ b/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/miscellaneous/GetAllMessagesRequest.java
@@ -0,0 +1,12 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.api1.model.miscellaneous;
+
+public class GetAllMessagesRequest {
+
+ // add locale here?
+
+}
diff --git a/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/miscellaneous/GetAllMessagesResult.java b/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/miscellaneous/GetAllMessagesResult.java
new file mode 100644
index 00000000..a2c72dbe
--- /dev/null
+++ b/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/miscellaneous/GetAllMessagesResult.java
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.api1.model.miscellaneous;
+
+import java.util.Map;
+
+public class GetAllMessagesResult {
+
+ /**
+ * This is a key-value pair map of the localization messages.
+ */
+
+ public Map messages;
+
+}
diff --git a/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/pkg/GetPkgRequest.java b/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/pkg/GetPkgRequest.java
new file mode 100644
index 00000000..37b82d9e
--- /dev/null
+++ b/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/pkg/GetPkgRequest.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.api1.model.pkg;
+
+public class GetPkgRequest {
+
+ /**
+ * This type defines the versions that should be sent back in the result. If the client were
+ * only interested in the latest version for example, then it should use the "LATEST" value.
+ */
+
+ public enum VersionType {
+ LATEST
+ }
+
+ /**
+ * This is the name of the package that you wish to obtain.
+ */
+
+ public String name;
+
+ /**
+ * Only a version of the package for this architecture will be returned. Note that this also
+ * includes the pseudo-architectures "any" and "source".
+ */
+
+ public String architectureCode;
+
+ public VersionType versionType;
+
+ // TODO - natural language
+
+}
diff --git a/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/pkg/GetPkgResult.java b/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/pkg/GetPkgResult.java
new file mode 100644
index 00000000..33881555
--- /dev/null
+++ b/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/pkg/GetPkgResult.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.api1.model.pkg;
+
+import java.util.List;
+
+/**
+ * This is the result model that comes back from the get packages API invocation.
+ */
+
+public class GetPkgResult {
+
+ public String name;
+
+ public List versions;
+
+ public static class Version {
+
+ public String major;
+ public String minor;
+ public String micro;
+ public String preRelease;
+ public Integer revision;
+
+ public String architectureCode;
+ public String summary;
+ public String description;
+ public String repositoryCode;
+
+ public List licenses;
+ public List copyrights;
+ public List urls;
+
+ }
+
+ public static class Url {
+
+ public String url;
+ public String urlTypeCode;
+
+ }
+
+}
diff --git a/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/pkg/SearchPkgsRequest.java b/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/pkg/SearchPkgsRequest.java
new file mode 100644
index 00000000..79fb4b0a
--- /dev/null
+++ b/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/pkg/SearchPkgsRequest.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.api1.model.pkg;
+
+/**
+ * This is the model object that is used to define the request to search for packages in the system.
+ */
+
+public class SearchPkgsRequest {
+
+ public enum ExpressionType {
+ CONTAINS
+ }
+
+ public String expression;
+
+ public String architectureCode;
+
+ public ExpressionType expressionType;
+
+ public Integer offset;
+
+ public Integer limit;
+
+
+}
diff --git a/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/pkg/SearchPkgsResult.java b/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/pkg/SearchPkgsResult.java
new file mode 100644
index 00000000..63897ee7
--- /dev/null
+++ b/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/pkg/SearchPkgsResult.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.api1.model.pkg;
+
+import java.util.List;
+
+public class SearchPkgsResult {
+
+ public List pkgs;
+
+ public Boolean hasMore;
+
+ public static class Pkg {
+ public String name;
+ public Version version;
+ }
+
+ public static class Version {
+ public String major;
+ public String minor;
+ public String micro;
+ public String preRelease;
+ public Integer revision;
+ }
+
+}
diff --git a/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/user/AuthenticateUserRequest.java b/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/user/AuthenticateUserRequest.java
new file mode 100644
index 00000000..84f73525
--- /dev/null
+++ b/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/user/AuthenticateUserRequest.java
@@ -0,0 +1,13 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.api1.model.user;
+
+public class AuthenticateUserRequest {
+
+ public String nickname;
+ public String passwordClear;
+
+}
diff --git a/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/user/AuthenticateUserResult.java b/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/user/AuthenticateUserResult.java
new file mode 100644
index 00000000..db6c41ec
--- /dev/null
+++ b/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/user/AuthenticateUserResult.java
@@ -0,0 +1,12 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.api1.model.user;
+
+public class AuthenticateUserResult {
+
+ public Boolean authenticated;
+
+}
diff --git a/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/user/CreateUserRequest.java b/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/user/CreateUserRequest.java
new file mode 100644
index 00000000..105ff72b
--- /dev/null
+++ b/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/user/CreateUserRequest.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.api1.model.user;
+
+public class CreateUserRequest {
+
+ public String nickname;
+ public String passwordClear;
+
+ /**
+ * The captcha token is obtained from an earlier invocation to the
+ * {@link org.haikuos.haikudepotserver.api1.CaptchaApi} method to get
+ * a captcha. This identifies the captcha for which the captcha response should
+ * correlate.
+ */
+
+ public String captchaToken;
+
+ /**
+ * This is the human-supplied text string that matches the image that would have been
+ * provided with the captcha that is identified by the cpatchaToken.
+ */
+
+ public String captchaResponse;
+
+}
diff --git a/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/user/CreateUserResult.java b/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/user/CreateUserResult.java
new file mode 100644
index 00000000..aadc1713
--- /dev/null
+++ b/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/user/CreateUserResult.java
@@ -0,0 +1,9 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.api1.model.user;
+
+public class CreateUserResult {
+}
diff --git a/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/user/GetUserRequest.java b/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/user/GetUserRequest.java
new file mode 100644
index 00000000..b0078dcd
--- /dev/null
+++ b/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/user/GetUserRequest.java
@@ -0,0 +1,12 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.api1.model.user;
+
+public class GetUserRequest {
+
+ public String nickname;
+
+}
diff --git a/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/user/GetUserResult.java b/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/user/GetUserResult.java
new file mode 100644
index 00000000..d58a5bca
--- /dev/null
+++ b/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/user/GetUserResult.java
@@ -0,0 +1,12 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.api1.model.user;
+
+public class GetUserResult {
+
+ public String nickname;
+
+}
diff --git a/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/support/CaptchaBadResponseException.java b/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/support/CaptchaBadResponseException.java
new file mode 100644
index 00000000..b3515535
--- /dev/null
+++ b/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/support/CaptchaBadResponseException.java
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.api1.support;
+
+/**
+ * This exception is thrown in the case where the user has mis-entered a captcha.
+ */
+
+public class CaptchaBadResponseException extends RuntimeException {
+
+ public CaptchaBadResponseException() {
+ super();
+ }
+
+}
diff --git a/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/support/Constants.java b/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/support/Constants.java
new file mode 100644
index 00000000..c1cb574a
--- /dev/null
+++ b/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/support/Constants.java
@@ -0,0 +1,14 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.api1.support;
+
+public interface Constants {
+
+ public final static int ERROR_CODE_VALIDATION = -32800;
+ public final static int ERROR_CODE_OBJECTNOTFOUND = -32801;
+ public final static int ERROR_CODE_CAPTCHABADRESPONSE = -32802;
+
+}
diff --git a/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/support/ObjectNotFoundException.java b/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/support/ObjectNotFoundException.java
new file mode 100644
index 00000000..2db3f056
--- /dev/null
+++ b/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/support/ObjectNotFoundException.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.api1.support;
+
+/**
+ * This exception is thrown when the system is not able to find an object in the system as part of an API
+ * invocation.
+ */
+
+public class ObjectNotFoundException extends Exception {
+
+ public String entityName;
+ public Object identifier;
+
+ public ObjectNotFoundException(String entityName, Object identifier) {
+ super();
+
+ if(null==entityName || 0==entityName.length()) {
+ throw new IllegalStateException("the entity name is required");
+ }
+
+ if(null==identifier) {
+ throw new IllegalStateException("the identifier is required");
+ }
+
+ this.entityName = entityName;
+ this.identifier = identifier;
+ }
+
+ public String getEntityName() {
+ return entityName;
+ }
+
+ public Object getIdentifier() {
+ return identifier;
+ }
+
+ @Override
+ public String getMessage() {
+ return String.format("the entity %s was not able to be found with the identifier %s",getEntityName(),getIdentifier().toString());
+ }
+}
diff --git a/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/support/ValidationException.java b/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/support/ValidationException.java
new file mode 100644
index 00000000..0838af33
--- /dev/null
+++ b/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/support/ValidationException.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.api1.support;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * This exception is thrown in the situation where a validation exception is thrown outside of the Cayenne
+ * infrastructure.
+ */
+
+public class ValidationException extends RuntimeException {
+
+ protected List validationFailures;
+
+ public ValidationException(ValidationFailure validationFailure) {
+ super();
+
+ if(null==validationFailure) {
+ throw new IllegalStateException();
+ }
+
+ this.validationFailures = Collections.singletonList(validationFailure);
+ }
+
+ public ValidationException(List validationFailures) {
+ super();
+
+ if(null==validationFailures) {
+ throw new IllegalStateException();
+ }
+
+ this.validationFailures = validationFailures;
+ }
+
+ public List getValidationFailures() {
+ return validationFailures;
+ }
+
+ @Override
+ public String getMessage() {
+ return String.format("%d validation failures",validationFailures.size());
+ }
+
+}
diff --git a/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/support/ValidationFailure.java b/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/support/ValidationFailure.java
new file mode 100644
index 00000000..ab2eae74
--- /dev/null
+++ b/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/support/ValidationFailure.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.api1.support;
+
+/**
+ * This object models a validation failure in the system. The property indicates what element of the object in
+ * question has failed validation checks and the message indicates what was wrong with that element.
+ */
+
+public class ValidationFailure {
+
+ private String property;
+ private String message;
+
+ public ValidationFailure(String property, String message) {
+ super();
+
+ if(null==property || 0==property.length()) {
+ throw new IllegalStateException("the property is required for a validation failure");
+ }
+
+ if(null==message || 0==message.length()) {
+ throw new IllegalStateException("the message is required for a validation failure");
+ }
+
+ this.property = property;
+ this.message = message;
+ }
+
+ public String getProperty() {
+ return property;
+ }
+
+ public String getMessage() {
+ return message;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("%s; %s",getProperty(),getMessage());
+ }
+
+}
diff --git a/haikudepotserver-packagefile/pom.xml b/haikudepotserver-packagefile/pom.xml
new file mode 100644
index 00000000..fa29e93c
--- /dev/null
+++ b/haikudepotserver-packagefile/pom.xml
@@ -0,0 +1,53 @@
+
+
+
+ haikudepotserver-parent
+ org.haikuos
+ ../haikudepotserver-parent
+ 1.0.1-SNAPSHOT
+
+
+ 4.0.0
+ org.haikuos
+ haikudepotserver-packagefile
+ jar
+
+
+
+
+ args4j
+ args4j
+
+
+
+ com.google.guava
+ guava
+
+
+
+ org.slf4j
+ slf4j-api
+
+
+ ch.qos.logback
+ logback-classic
+
+
+
+ junit
+ junit
+ test
+
+
+
+ org.easytesting
+ fest-assert
+ test
+
+
+
+
+
diff --git a/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/AttributeContext.java b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/AttributeContext.java
new file mode 100644
index 00000000..1dff0367
--- /dev/null
+++ b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/AttributeContext.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.pkg;
+
+import org.haikuos.pkg.heap.HeapReader;
+
+/**
+ * This object carries around pointers to other data structures and model objects that are required to
+ * support the processing of attributes.
+ */
+
+public class AttributeContext {
+
+ private StringTable stringTable;
+
+ private HeapReader heapReader;
+
+ public HeapReader getHeapReader() {
+ return heapReader;
+ }
+
+ public void setHeapReader(HeapReader heapReader) {
+ this.heapReader = heapReader;
+ }
+
+ public StringTable getStringTable() {
+ return stringTable;
+ }
+
+ public void setStringTable(StringTable stringTable) {
+ this.stringTable = stringTable;
+ }
+
+}
diff --git a/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/AttributeIterator.java b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/AttributeIterator.java
new file mode 100644
index 00000000..885b12db
--- /dev/null
+++ b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/AttributeIterator.java
@@ -0,0 +1,315 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.pkg;
+
+import com.google.common.base.Charsets;
+import com.google.common.base.Preconditions;
+import com.google.common.io.ByteArrayDataOutput;
+import com.google.common.io.ByteStreams;
+import org.haikuos.pkg.heap.HeapCoordinates;
+import org.haikuos.pkg.model.*;
+
+import java.math.BigInteger;
+import java.util.Iterator;
+
+/**
+ * This object is able to provide an iterator through all of the attributes at a given offset in the chunks. The
+ * chunk data is supplied through an instance of {@link AttributeContext}. It will work through all of the
+ * attributes serially and will also process all of the child-attributes as well. The iteration process means that
+ * less in-memory data is required to process a relatively long list of attributes.
+ *
+ * Use the method {@link #hasNext()} to find out if there is another attribute to read and {@link #next()} in
+ * order to obtain the next attribute.
+ *
+ * Note that this does not actually implement {@link Iterator} because it needs to throw Hpk exceptions
+ * which would mean that it were not compliant with the @{link Iterator} interface.
+ */
+
+public class AttributeIterator {
+
+ private final static int ATTRIBUTE_TYPE_INVALID = 0;
+ private final static int ATTRIBUTE_TYPE_INT = 1;
+ private final static int ATTRIBUTE_TYPE_UINT = 2;
+ private final static int ATTRIBUTE_TYPE_STRING = 3;
+ private final static int ATTRIBUTE_TYPE_RAW = 4;
+
+ private final static int ATTRIBUTE_ENCODING_INT_8_BIT = 0;
+ private final static int ATTRIBUTE_ENCODING_INT_16_BIT = 1;
+ private final static int ATTRIBUTE_ENCODING_INT_32_BIT = 2;
+ private final static int ATTRIBUTE_ENCODING_INT_64_BIT = 3;
+
+ private final static int ATTRIBUTE_ENCODING_STRING_INLINE = 0;
+ private final static int ATTRIBUTE_ENCODING_STRING_TABLE = 1;
+
+ private final static int ATTRIBUTE_ENCODING_RAW_INLINE = 0;
+ private final static int ATTRIBUTE_ENCODING_RAW_HEAP = 1;
+
+ private long offset;
+
+ private AttributeContext context;
+
+ private BigInteger nextTag = null;
+
+ public AttributeIterator(AttributeContext context, long offset) {
+ super();
+
+ Preconditions.checkNotNull(context);
+ Preconditions.checkState(offset >= 0 && offset < Integer.MAX_VALUE);
+
+ this.offset = offset;
+ this.context = context;
+ }
+
+ public AttributeContext getContext() {
+ return context;
+ }
+
+ public long getOffset() {
+ return offset;
+ }
+
+ private int deriveAttributeTagType(BigInteger tag) {
+ return tag.subtract(BigInteger.valueOf(1l)).shiftRight(7).and(BigInteger.valueOf(0x7l)).intValue();
+ }
+
+ private int deriveAttributeTagId(BigInteger tag) {
+ return tag.subtract(BigInteger.valueOf(1l)).and(BigInteger.valueOf(0x7fl)).intValue();
+ }
+
+ private int deriveAttributeTagEncoding(BigInteger tag) {
+ return tag.subtract(BigInteger.valueOf(1l)).shiftRight(11).and(BigInteger.valueOf(3l)).intValue();
+ }
+
+ private boolean deriveAttributeTagHasChildAttributes(BigInteger tag) {
+ return 0!=tag.subtract(BigInteger.valueOf(1l)).shiftRight(10).and(BigInteger.valueOf(1l)).intValue();
+ }
+
+ private BigInteger getNextTag() {
+ if(null==nextTag) {
+ nextTag = readUnsignedLeb128();
+ }
+
+ return nextTag;
+ }
+
+ private BigInteger readUnsignedLeb128() {
+ BigInteger result = BigInteger.valueOf(0l);
+ int shift = 0;
+
+ while(true) {
+ int b = context.getHeapReader().readHeap(offset);
+ offset++;
+
+ result = result.or(BigInteger.valueOf((long) (b & 0x7f)).shiftLeft(shift));
+
+ if(0 == (b & 0x80)) {
+ return result;
+ }
+
+ shift+=7;
+ }
+ }
+
+ private void ensureValidEncodingForInt(int encoding) {
+ switch(encoding) {
+ case ATTRIBUTE_ENCODING_INT_8_BIT:
+ case ATTRIBUTE_ENCODING_INT_16_BIT:
+ case ATTRIBUTE_ENCODING_INT_32_BIT:
+ case ATTRIBUTE_ENCODING_INT_64_BIT:
+ break;
+
+ default:
+ throw new IllegalStateException("unknown encoding on a signed integer");
+ }
+ }
+
+ /**
+ * This method allows the caller to discover if there is another attribute to get off the iterator.
+ */
+
+ public boolean hasNext() {
+ return 0!=getNextTag().signum();
+ }
+
+ /**
+ * This method will return the next {@link org.haikuos.pkg.model.Attribute}. If there is not another value to return then
+ * this method will return null. It will throw an instance of @{link HpkException} in any situation in which
+ * it is not able to parse the data or chunks such that it is not able to read the next attribute.
+ */
+
+ public Attribute next() throws HpkException {
+
+ Attribute result = null;
+
+ // first, the LEB128 has to be read in which is the 'tag' defining what sort of attribute this is that
+ // we are dealing with.
+
+ BigInteger tag = getNextTag();
+
+ // if we encounter 0 tag then we know that we have finished the list.
+
+ if(0!=tag.signum()) {
+
+ int encoding = deriveAttributeTagEncoding(tag);
+ int id = deriveAttributeTagId(tag);
+
+ if(id <= 0 || id >= AttributeId.values().length) {
+ throw new HpkException("illegal id; "+Integer.toString(id));
+ }
+ AttributeId attributeId = AttributeId.values()[id];
+
+ switch(deriveAttributeTagType(tag)) {
+
+ case ATTRIBUTE_TYPE_INVALID:
+ throw new HpkException("an invalid attribute tag type has been encountered");
+
+ case ATTRIBUTE_TYPE_INT:
+ {
+ ensureValidEncodingForInt(encoding);
+ byte[] buffer = new byte[encoding+1];
+ context.getHeapReader().readHeap(buffer,0,new HeapCoordinates(offset,encoding+1));
+ offset+=encoding+1;
+ result = new IntAttribute(attributeId, new BigInteger(buffer));
+ }
+ break;
+
+ case ATTRIBUTE_TYPE_UINT:
+ {
+ ensureValidEncodingForInt(encoding);
+ byte[] buffer = new byte[encoding+1];
+ context.getHeapReader().readHeap(buffer,0,new HeapCoordinates(offset,encoding+1));
+ offset+=encoding+1;
+ result = new IntAttribute(attributeId, new BigInteger(1,buffer));
+ }
+ break;
+
+ case ATTRIBUTE_TYPE_STRING:
+ {
+ switch(encoding) {
+
+ case ATTRIBUTE_ENCODING_STRING_INLINE:
+ {
+ ByteArrayDataOutput assembly = ByteStreams.newDataOutput();
+
+ while(null==result) {
+ int b = context.getHeapReader().readHeap(offset);
+ offset++;
+
+ if(0!=b) {
+ assembly.write(b);
+ }
+ else {
+ result = new StringInlineAttribute(
+ attributeId,
+ new String(
+ assembly.toByteArray(),
+ Charsets.UTF_8));
+ }
+ }
+ }
+ break;
+
+ case ATTRIBUTE_ENCODING_STRING_TABLE:
+ {
+ BigInteger index = readUnsignedLeb128();
+
+ if(index.compareTo(BigInteger.valueOf(Integer.MAX_VALUE)) > 0) {
+ throw new IllegalStateException("the string table index is preposterously large");
+ }
+
+ result = new StringTableRefAttribute(attributeId,index.intValue());
+ }
+ break;
+
+ default:
+ throw new HpkException("unknown string encoding; "+encoding);
+ }
+ }
+ break;
+
+ case ATTRIBUTE_TYPE_RAW:
+ {
+ switch(encoding) {
+ case ATTRIBUTE_ENCODING_RAW_INLINE:
+ {
+ BigInteger length = readUnsignedLeb128();
+
+ if(length.compareTo(BigInteger.valueOf(Integer.MAX_VALUE)) > 0) {
+ throw new HpkException("the length of the inline data is too large");
+ }
+
+ byte[] buffer = new byte[length.intValue()];
+ context.getHeapReader().readHeap(buffer,0,new HeapCoordinates(offset,length.intValue()));
+ offset += length.intValue();
+
+ result = new RawInlineAttribute(attributeId, buffer);
+ }
+ break;
+
+ case ATTRIBUTE_ENCODING_RAW_HEAP:
+ {
+ BigInteger rawLength = readUnsignedLeb128();
+ BigInteger rawOffset = readUnsignedLeb128();
+
+ if(rawLength.compareTo(BigInteger.valueOf(Integer.MAX_VALUE)) > 0) {
+ throw new HpkException("the length of the heap data is too large");
+ }
+
+ if(rawOffset.compareTo(BigInteger.valueOf(Integer.MAX_VALUE)) > 0) {
+ throw new HpkException("the offset of the heap data is too large");
+ }
+
+ result = new RawHeapAttribute(
+ attributeId,
+ new HeapCoordinates(
+ rawOffset.longValue(),
+ rawLength.longValue()));
+ }
+
+ default:
+ throw new HpkException("unknown raw encoding; "+encoding);
+ }
+ }
+ break;
+
+ default:
+ throw new HpkException("unable to read the tag type; "+deriveAttributeTagType(tag));
+
+ }
+
+ // each attribute id has a type associated with it; now check that the attribute matches
+ // its intended type.
+
+ if(result.getAttributeId().getAttributeType() != result.getAttributeType()) {
+ throw new HpkException(String.format(
+ "mismatch in attribute type for id %s; expecting %s, but got %s",
+ result.getAttributeId().getName(),
+ result.getAttributeId().getAttributeType(),
+ result.getAttributeType()));
+ }
+
+ // possibly there are child attributes after this attribute; if this is the
+ // case then open-up a new iterator to work across those and load them in.
+
+ if(deriveAttributeTagHasChildAttributes(tag)) {
+
+ AttributeIterator childAttributeIterator = new AttributeIterator(context, offset);
+
+ while(childAttributeIterator.hasNext()) {
+ result.addChildAttribute(childAttributeIterator.next());
+ }
+
+ offset = childAttributeIterator.getOffset();
+
+ }
+
+ nextTag = null;
+ }
+
+ return result;
+ }
+
+}
diff --git a/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/FileHelper.java b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/FileHelper.java
new file mode 100644
index 00000000..6ce1278d
--- /dev/null
+++ b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/FileHelper.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.pkg;
+
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.math.BigInteger;
+
+/**
+ * This helps out with typical common reads that might be performed as part of
+ * parsing various values in the HPKR file.
+ */
+
+public class FileHelper {
+
+ private final static BigInteger MAX_BIGINTEGER_FILE = new BigInteger(Long.toString(Long.MAX_VALUE));
+
+ private byte[] buffer8 = new byte[8];
+
+ public int readUnsignedShortToInt(RandomAccessFile randomAccessFile) throws IOException, HpkException {
+
+ if(2!=randomAccessFile.read(buffer8,0,2)) {
+ throw new HpkException("not enough bytes read for an unsigned short");
+ }
+
+ int i0 = buffer8[0]&0xff;
+ int i1 = buffer8[1]&0xff;
+
+ return i0 << 8 | i1;
+ }
+
+ public long readUnsignedIntToLong(RandomAccessFile randomAccessFile) throws IOException, HpkException {
+
+ if(4!=randomAccessFile.read(buffer8,0,4)) {
+ throw new HpkException("not enough bytes read for an unsigned int");
+ }
+
+ long l0 = buffer8[0]&0xff;
+ long l1 = buffer8[1]&0xff;
+ long l2 = buffer8[2]&0xff;
+ long l3 = buffer8[3]&0xff;
+
+ return l0 << 24 | l1 << 16 | l2 << 8 | l3;
+ }
+
+ public BigInteger readUnsignedLong(RandomAccessFile randomAccessFile) throws IOException, HpkException {
+
+ if(8!=randomAccessFile.read(buffer8)) {
+ throw new HpkException("not enough bytes read for an unsigned long");
+ }
+
+ return new BigInteger(1, buffer8);
+ }
+
+ public long readUnsignedLongToLong(RandomAccessFile randomAccessFile) throws IOException, HpkException {
+
+ BigInteger result = readUnsignedLong(randomAccessFile);
+
+ if(result.compareTo(MAX_BIGINTEGER_FILE) > 0) {
+ throw new HpkException("the hpkr file contains an unsigned long which is larger than can be represented in a java long");
+ }
+
+ return result.longValue();
+ }
+
+ public char[] readMagic(RandomAccessFile randomAccessFile) throws IOException, HpkException {
+
+ if(4!=randomAccessFile.read(buffer8,0,4)) {
+ throw new HpkException("not enough bytes read for a 4-byte magic");
+ }
+
+ return new char[] {
+ (char) buffer8[0],
+ (char) buffer8[1],
+ (char) buffer8[2],
+ (char) buffer8[3]
+ };
+ }
+
+
+}
diff --git a/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/HpkException.java b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/HpkException.java
new file mode 100644
index 00000000..9e380b92
--- /dev/null
+++ b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/HpkException.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.pkg;
+
+/**
+ * This type of exception is used through the Hpk file processing system to indicate that something has gone wrong
+ * with processing the Hpk data in some way.
+ */
+
+public class HpkException extends Exception {
+
+ public HpkException(String message) {
+ super(message);
+ }
+
+ public HpkException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+}
diff --git a/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/HpkStringTable.java b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/HpkStringTable.java
new file mode 100644
index 00000000..847c0b04
--- /dev/null
+++ b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/HpkStringTable.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.pkg;
+
+import com.google.common.base.Charsets;
+import com.google.common.base.Preconditions;
+import org.haikuos.pkg.heap.HeapCoordinates;
+import org.haikuos.pkg.heap.HpkHeapReader;
+
+/**
+ * The HPK* file format may contain a table of commonly used strings in a table. This object will represent
+ * those strings and will lazily load them from the heap as necessary.
+ */
+
+public class HpkStringTable implements StringTable {
+
+ private HpkHeapReader heapReader;
+
+ private long expectedCount;
+
+ private long heapLength;
+
+ private long heapOffset;
+
+ private String[] values = null;
+
+ public HpkStringTable(
+ HpkHeapReader heapReader,
+ long heapOffset,
+ long heapLength,
+ long expectedCount) {
+
+ super();
+
+ Preconditions.checkNotNull(heapReader);
+ Preconditions.checkState(heapOffset >= 0 && heapOffset < Integer.MAX_VALUE);
+ Preconditions.checkState(heapLength >= 0 && heapLength < Integer.MAX_VALUE);
+ Preconditions.checkState(expectedCount >= 0 && expectedCount < Integer.MAX_VALUE);
+
+ this.heapReader = heapReader;
+ this.expectedCount = expectedCount;
+ this.heapOffset = heapOffset;
+ this.heapLength = heapLength;
+
+ }
+
+ // TODO; could avoid the big read into a buffer by reading the heap byte by byte.
+ private String[] readStrings() throws HpkException {
+ String[] result = new String[(int) expectedCount];
+ byte[] stringsDataBuffer = new byte[(int) heapLength];
+
+ heapReader.readHeap(
+ stringsDataBuffer,
+ 0,
+ new HeapCoordinates(
+ heapOffset,
+ heapLength));
+
+ // now work through the data and load them into the strings.
+
+ int stringIndex = 0;
+ int offset = 0;
+
+ while(offset < stringsDataBuffer.length) {
+
+ if(0==stringsDataBuffer[offset]) {
+ if(stringIndex != result.length) {
+ throw new HpkException(String.format("expected to read %d package strings from the strings table, but actually found %d",expectedCount,stringIndex));
+ }
+
+ return result;
+ }
+
+ int start = offset;
+
+ while(0!=stringsDataBuffer[offset] && offset < stringsDataBuffer.length) {
+ offset++;
+ }
+
+ if(offset < stringsDataBuffer.length) {
+ result[stringIndex] = new String(stringsDataBuffer,start,offset-start, Charsets.UTF_8);
+ stringIndex++;
+ offset++;
+ }
+
+ }
+
+ throw new HpkException("expected to find the null-terminator for the list of strings, but was not able to find one.");
+ }
+
+ private String[] getStrings() throws HpkException {
+ if(null==values) {
+ if(0==heapLength) {
+ values = new String[] {};
+ }
+ else {
+ values = readStrings();
+ }
+ }
+
+ return values;
+ }
+
+ @Override
+ public String getString(int index) throws HpkException {
+ return getStrings()[index];
+ }
+
+}
diff --git a/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/HpkrFileExtractor.java b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/HpkrFileExtractor.java
new file mode 100644
index 00000000..e0d08465
--- /dev/null
+++ b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/HpkrFileExtractor.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.pkg;
+
+import com.google.common.base.Preconditions;
+import org.haikuos.pkg.heap.HeapCompression;
+import org.haikuos.pkg.heap.HpkHeapReader;
+
+import java.io.Closeable;
+import java.io.File;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.util.Arrays;
+
+/**
+ * This object represents an object that can extract an Hpkr (Haiku Pkg Repository) file. If you are wanting to
+ * read HPKR files then you should instantiate an instance of this class and then make method calls to it in order to
+ * read values such as the attributes of the HPKR file.
+ */
+
+public class HpkrFileExtractor implements Closeable {
+
+ private File file;
+
+ private HpkrHeader header;
+
+ private HpkHeapReader heapReader;
+
+ private HpkStringTable attributesStringTable;
+
+ public HpkrFileExtractor(File file) throws IOException, HpkException {
+
+ super();
+ Preconditions.checkNotNull(file);
+ Preconditions.checkState(file.isFile() && file.exists());
+
+ this.file = file;
+ this.header = readHeader();
+
+ try {
+ heapReader = new HpkHeapReader(
+ file,
+ header.getHeapCompression(),
+ header.getHeaderSize(),
+ header.getHeapChunkSize(), // uncompressed size
+ header.getHeapSizeCompressed(), // including the compressed chunk lengths.
+ header.getHeapSizeUncompressed() // excludes the compressed chunk lengths.
+ );
+
+ attributesStringTable = new HpkStringTable(
+ heapReader,
+ header.getInfoLength(),
+ header.getPackagesStringsLength(),
+ header.getPackagesStringsCount());
+
+ }
+ catch(Exception e) {
+ close();
+ throw new HpkException("unable to setup the hpkr file extractor",e);
+ }
+ catch(Throwable th) {
+ close();
+ throw new RuntimeException("unable to setup the hpkr file extractor",th);
+ }
+ }
+
+ @Override
+ public void close() {
+ if(null!=heapReader) {
+ heapReader.close();
+ }
+ }
+
+ public AttributeContext getAttributeContext() {
+ AttributeContext context = new AttributeContext();
+ context.setHeapReader(heapReader);
+ context.setStringTable(attributesStringTable);
+ return context;
+ }
+
+ public AttributeIterator getPackageAttributesIterator() {
+ long offset = header.getInfoLength() + header.getPackagesStringsLength();
+ return new AttributeIterator(getAttributeContext(),offset);
+ }
+
+ private HpkrHeader readHeader() throws IOException, HpkException {
+ Preconditions.checkNotNull(file);
+
+ RandomAccessFile randomAccessFile = null;
+ FileHelper fileHelper = new FileHelper();
+
+ try {
+ randomAccessFile = new RandomAccessFile(file, "r");
+
+ if(!Arrays.equals(new char[] { 'h','p','k','r' }, fileHelper.readMagic(randomAccessFile))) {
+ throw new HpkException("magic incorrect at the start of the hpkr file");
+ }
+
+ HpkrHeader result = new HpkrHeader();
+
+ result.setHeaderSize(fileHelper.readUnsignedShortToInt(randomAccessFile));
+ result.setVersion(fileHelper.readUnsignedShortToInt(randomAccessFile));
+ result.setTotalSize(fileHelper.readUnsignedLongToLong(randomAccessFile));
+ result.setMinorVersion(fileHelper.readUnsignedShortToInt(randomAccessFile));
+
+ int compression = fileHelper.readUnsignedShortToInt(randomAccessFile);
+
+ // heap information
+ switch(compression) {
+ case 0:
+ result.setHeapCompression(HeapCompression.NONE);
+ break;
+
+ case 1:
+ result.setHeapCompression(HeapCompression.ZLIB);
+ break;
+
+ default:
+ throw new HpkException("unknown compression setting in header; "+compression);
+ }
+
+ result.setHeapChunkSize(fileHelper.readUnsignedIntToLong(randomAccessFile));
+ result.setHeapSizeCompressed(fileHelper.readUnsignedLongToLong(randomAccessFile));
+ result.setHeapSizeUncompressed(fileHelper.readUnsignedLongToLong(randomAccessFile));
+
+ // repository info
+ result.setInfoLength(fileHelper.readUnsignedIntToLong(randomAccessFile));
+ randomAccessFile.skipBytes(4); // reserved
+
+ // package attributes section
+ result.setPackagesLength(fileHelper.readUnsignedLongToLong(randomAccessFile));
+ result.setPackagesStringsLength(fileHelper.readUnsignedLongToLong(randomAccessFile));
+ result.setPackagesStringsCount(fileHelper.readUnsignedLongToLong(randomAccessFile));
+
+ return result;
+ }
+ finally {
+ if(null!=randomAccessFile) {
+ try {
+ randomAccessFile.close();
+ }
+ catch(IOException ioe) {
+ // ignore
+ }
+ }
+ }
+ }
+
+}
diff --git a/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/HpkrHeader.java b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/HpkrHeader.java
new file mode 100644
index 00000000..3fa53bf4
--- /dev/null
+++ b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/HpkrHeader.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.pkg;
+
+import org.haikuos.pkg.heap.HeapCompression;
+
+public class HpkrHeader {
+
+ private long headerSize;
+ private int version;
+ private long totalSize;
+ private int minorVersion;
+
+ // heap
+ private HeapCompression heapCompression;
+ private long heapChunkSize;
+ private long heapSizeCompressed;
+ private long heapSizeUncompressed;
+
+ // repository info section
+ private long infoLength;
+
+ // package attributes section
+ private long packagesLength;
+ private long packagesStringsLength;
+ private long packagesStringsCount;
+
+ public long getHeaderSize() {
+ return headerSize;
+ }
+
+ public void setHeaderSize(long headerSize) {
+ this.headerSize = headerSize;
+ }
+
+ public int getVersion() {
+ return version;
+ }
+
+ public void setVersion(int version) {
+ this.version = version;
+ }
+
+ public long getTotalSize() {
+ return totalSize;
+ }
+
+ public void setTotalSize(long totalSize) {
+ this.totalSize = totalSize;
+ }
+
+ public int getMinorVersion() {
+ return minorVersion;
+ }
+
+ public void setMinorVersion(int minorVersion) {
+ this.minorVersion = minorVersion;
+ }
+
+ public HeapCompression getHeapCompression() {
+ return heapCompression;
+ }
+
+ public void setHeapCompression(HeapCompression heapCompression) {
+ this.heapCompression = heapCompression;
+ }
+
+ public long getHeapChunkSize() {
+ return heapChunkSize;
+ }
+
+ public void setHeapChunkSize(long heapChunkSize) {
+ this.heapChunkSize = heapChunkSize;
+ }
+
+ public long getHeapSizeCompressed() {
+ return heapSizeCompressed;
+ }
+
+ public void setHeapSizeCompressed(long heapSizeCompressed) {
+ this.heapSizeCompressed = heapSizeCompressed;
+ }
+
+ public long getHeapSizeUncompressed() {
+ return heapSizeUncompressed;
+ }
+
+ public void setHeapSizeUncompressed(long heapSizeUncompressed) {
+ this.heapSizeUncompressed = heapSizeUncompressed;
+ }
+
+ public long getInfoLength() {
+ return infoLength;
+ }
+
+ public void setInfoLength(long infoLength) {
+ this.infoLength = infoLength;
+ }
+
+ public long getPackagesLength() {
+ return packagesLength;
+ }
+
+ public void setPackagesLength(long packagesLength) {
+ this.packagesLength = packagesLength;
+ }
+
+ public long getPackagesStringsLength() {
+ return packagesStringsLength;
+ }
+
+ public void setPackagesStringsLength(long packagesStringsLength) {
+ this.packagesStringsLength = packagesStringsLength;
+ }
+
+ public long getPackagesStringsCount() {
+ return packagesStringsCount;
+ }
+
+ public void setPackagesStringsCount(long packagesStringsCount) {
+ this.packagesStringsCount = packagesStringsCount;
+ }
+
+}
+
diff --git a/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/PkgException.java b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/PkgException.java
new file mode 100644
index 00000000..aafa5071
--- /dev/null
+++ b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/PkgException.java
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.pkg;
+
+public class PkgException extends Exception {
+
+ public PkgException(String message) {
+ super(message);
+ }
+
+ public PkgException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+}
diff --git a/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/PkgFactory.java b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/PkgFactory.java
new file mode 100644
index 00000000..6c8be8ae
--- /dev/null
+++ b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/PkgFactory.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.pkg;
+
+import com.google.common.base.Optional;
+import com.google.common.base.Preconditions;
+import org.haikuos.pkg.model.*;
+
+import java.math.BigInteger;
+
+/**
+ * This object is algorithm that is able to convert a top level package attribute into a modelled package object
+ * that can more easily represent the package; essentially converting the low-level attributes into a higher-level
+ * package model object.
+ */
+
+public class PkgFactory {
+
+ private String getOptionalStringAttributeValue(
+ AttributeContext attributeContext,
+ Attribute attribute,
+ AttributeId attributeId) throws PkgException, HpkException {
+
+ Preconditions.checkNotNull(attribute);
+ Preconditions.checkNotNull(attributeContext);
+
+ Optional nameAttributeOptional = attribute.getChildAttribute(attributeId);
+
+ if(!nameAttributeOptional.isPresent()) {
+ return null;
+ }
+
+ return (String) nameAttributeOptional.get().getValue(attributeContext);
+ }
+
+ private String getRequiredStringAttributeValue(
+ AttributeContext attributeContext,
+ Attribute attribute,
+ AttributeId attributeId) throws PkgException, HpkException {
+
+ Preconditions.checkNotNull(attribute);
+ Preconditions.checkNotNull(attributeContext);
+
+ Optional nameAttributeOptional = attribute.getChildAttribute(attributeId);
+
+ if(!nameAttributeOptional.isPresent()) {
+ throw new PkgException(String.format("the %s attribute must be present",attributeId.getName()));
+ }
+
+ return (String) nameAttributeOptional.get().getValue(attributeContext);
+ }
+
+ private PkgVersion createVersion(
+ AttributeContext attributeContext,
+ Attribute attribute) throws PkgException, HpkException {
+
+ Preconditions.checkNotNull(attribute);
+ Preconditions.checkNotNull(attributeContext);
+ Preconditions.checkState(AttributeId.PACKAGE_VERSION_MAJOR == attribute.getAttributeId());
+
+ Optional revisionAttribute = attribute.getChildAttribute(AttributeId.PACKAGE_VERSION_REVISION);
+ Integer revision = null;
+
+ if(revisionAttribute.isPresent()) {
+ revision = ((BigInteger) ((IntAttribute) revisionAttribute.get()).getValue(attributeContext)).intValue();
+ }
+
+ return new PkgVersion(
+ (String) attribute.getValue(attributeContext),
+ getOptionalStringAttributeValue(attributeContext, attribute, AttributeId.PACKAGE_VERSION_MINOR),
+ getOptionalStringAttributeValue(attributeContext, attribute, AttributeId.PACKAGE_VERSION_MICRO),
+ getOptionalStringAttributeValue(attributeContext,attribute, AttributeId.PACKAGE_VERSION_PRE_RELEASE),
+ revision);
+
+ }
+
+ private PkgArchitecture createArchitecture(
+ AttributeContext attributeContext,
+ Attribute attribute) throws PkgException, HpkException {
+
+ Preconditions.checkNotNull(attribute);
+ Preconditions.checkNotNull(attributeContext);
+ Preconditions.checkState(AttributeId.PACKAGE_ARCHITECTURE == attribute.getAttributeId());
+
+ int value = ((BigInteger) attribute.getValue(attributeContext)).intValue();
+ return PkgArchitecture.values()[value];
+ }
+
+ public Pkg createPackage(
+ AttributeContext attributeContext,
+ Attribute attribute) throws PkgException {
+
+ Preconditions.checkNotNull(attribute);
+ Preconditions.checkNotNull(attributeContext);
+ Preconditions.checkState(attribute.getAttributeId() == AttributeId.PACKAGE);
+
+ Pkg result = new Pkg();
+
+ try {
+
+ result.setName(getRequiredStringAttributeValue(attributeContext, attribute, AttributeId.PACKAGE_NAME));
+ result.setVendor(getRequiredStringAttributeValue(attributeContext, attribute, AttributeId.PACKAGE_VENDOR));
+ result.setSummary(getOptionalStringAttributeValue(attributeContext, attribute, AttributeId.PACKAGE_SUMMARY));
+ result.setDescription(getOptionalStringAttributeValue(attributeContext, attribute, AttributeId.PACKAGE_DESCRIPTION));
+
+ result.setHomePageUrl(new PkgUrl(
+ getOptionalStringAttributeValue(attributeContext, attribute, AttributeId.PACKAGE_URL),
+ PkgUrlType.HOMEPAGE));
+
+ // get the architecture.
+
+ Optional architectureAttributeOptional = attribute.getChildAttribute(AttributeId.PACKAGE_ARCHITECTURE);
+
+ if(!architectureAttributeOptional.isPresent()) {
+ throw new PkgException(String.format("the attribute %s is required", AttributeId.PACKAGE_ARCHITECTURE));
+ }
+
+ result.setArchitecture(createArchitecture(attributeContext,architectureAttributeOptional.get()));
+
+ // get the version.
+
+ Optional majorVersionAttributeOptional = attribute.getChildAttribute(AttributeId.PACKAGE_VERSION_MAJOR);
+
+ if(!majorVersionAttributeOptional.isPresent()) {
+ throw new PkgException(String.format("the attribute %s is required", AttributeId.PACKAGE_VERSION_MAJOR));
+ }
+
+ result.setVersion(createVersion(attributeContext, majorVersionAttributeOptional.get()));
+
+ // get the copyrights.
+
+ for(Attribute copyrightAttribute : attribute.getChildAttributes(AttributeId.PACKAGE_COPYRIGHT)) {
+ if(copyrightAttribute.getAttributeType() == AttributeType.STRING) { // illegal not to be, but be lenient
+ result.addCopyright(copyrightAttribute.getValue(attributeContext).toString());
+ }
+ }
+
+ // get the licenses.
+
+ for(Attribute licenseAttribute : attribute.getChildAttributes(AttributeId.PACKAGE_LICENSE)) {
+ if(licenseAttribute.getAttributeType() == AttributeType.STRING) { // illegal not to be, but be lenient
+ result.addLicense(licenseAttribute.getValue(attributeContext).toString());
+ }
+ }
+
+
+ }
+ catch(HpkException he) {
+ throw new PkgException("unable to create a package owing to a problem with the hpk packaging",he);
+ }
+
+ return result;
+ }
+
+}
diff --git a/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/PkgIterator.java b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/PkgIterator.java
new file mode 100644
index 00000000..c185d391
--- /dev/null
+++ b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/PkgIterator.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.pkg;
+
+import com.google.common.base.Preconditions;
+import org.haikuos.pkg.model.Attribute;
+import org.haikuos.pkg.model.Pkg;
+
+/**
+ * This object will wrap an attribute iterator to be able to generate a series of {@link Pkg} objects that
+ * model a package in the HaikuOS package management system.
+ */
+
+public class PkgIterator {
+
+ private AttributeIterator attributeIterator;
+ private PkgFactory pkgFactory;
+
+ public PkgIterator(AttributeIterator attributeIterator) {
+ this(attributeIterator, new PkgFactory());
+ }
+
+ public PkgIterator(AttributeIterator attributeIterator, PkgFactory pkgFactory) {
+ super();
+ Preconditions.checkNotNull(attributeIterator);
+ this.attributeIterator = attributeIterator;
+ this.pkgFactory = pkgFactory;
+ }
+
+ /**
+ * This method will return true if there are more packages to be obtained from the attributes iterator.
+ * @return
+ */
+
+ public boolean hasNext() {
+ return attributeIterator.hasNext();
+ }
+
+ /**
+ * This method will return the next package from the attribute iterator supplied.
+ * @return The return value is the next package from the list of attributes.
+ * @throws PkgException when there is a problem obtaining the next package from the attributes.
+ * @throws HpkException when there is a problem obtaining the next attributes.
+ */
+
+ public Pkg next() throws PkgException, HpkException {
+ Attribute attribute = attributeIterator.next();
+
+ if(null!=attribute) {
+ return pkgFactory.createPackage(attributeIterator.getContext(), attribute);
+ }
+
+ return null;
+ }
+
+}
diff --git a/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/StringTable.java b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/StringTable.java
new file mode 100644
index 00000000..d878fc6b
--- /dev/null
+++ b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/StringTable.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.pkg;
+
+/**
+ * The attribute-reading elements of the system need to be able to access a string table. This is interface of
+ * an object which is able to provide those strings.
+ */
+
+public interface StringTable {
+
+ /**
+ * Given the index supplied, this method should return the corresponding string. It will throw an instance
+ * of {@link HpkException} if there is any problems associated with achieving this.
+ */
+
+ public String getString(int index) throws HpkException;
+
+}
diff --git a/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/heap/HeapCompression.java b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/heap/HeapCompression.java
new file mode 100644
index 00000000..4cd727f1
--- /dev/null
+++ b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/heap/HeapCompression.java
@@ -0,0 +1,11 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.pkg.heap;
+
+public enum HeapCompression {
+ NONE,
+ ZLIB
+}
diff --git a/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/heap/HeapCoordinates.java b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/heap/HeapCoordinates.java
new file mode 100644
index 00000000..4a1a9921
--- /dev/null
+++ b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/heap/HeapCoordinates.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.pkg.heap;
+
+import com.google.common.base.Preconditions;
+
+/**
+ * This object provides an offset and length into the heap and this provides a coordinate for a chunk of
+ * data in the heap. Note that the coordinates refer to the uncompressed data across all of the chunks of the heap.
+ *
+ */
+
+public class HeapCoordinates {
+
+ private long offset;
+ private long length;
+
+ public HeapCoordinates(long offset, long length) {
+ super();
+
+ Preconditions.checkState(offset >= 0 && offset < Integer.MAX_VALUE);
+ Preconditions.checkState(length >= 0 && length < Integer.MAX_VALUE);
+
+ this.offset = offset;
+ this.length = length;
+ }
+
+ public long getOffset() {
+ return offset;
+ }
+
+ public long getLength() {
+ return length;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ HeapCoordinates that = (HeapCoordinates) o;
+
+ if (length != that.length) return false;
+ if (offset != that.offset) return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = (int) (offset ^ (offset >>> 32));
+ result = 31 * result + (int) (length ^ (length >>> 32));
+ return result;
+ }
+
+ @Override
+
+ public String toString() {
+ return String.format("{%d,%d}",offset,length);
+ }
+
+
+}
diff --git a/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/heap/HeapReader.java b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/heap/HeapReader.java
new file mode 100644
index 00000000..721c0f86
--- /dev/null
+++ b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/heap/HeapReader.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.pkg.heap;
+
+/**
+ * This is an interface for classes that are able to provide data from a block of memory referred to as "the heap".
+ * Concrete sub-classes are able to provide specific implementations that can read from different on-disk files to
+ * provide access to a heap.
+ *
+ */
+
+public interface HeapReader {
+
+ /**
+ * This method reads from the heap (possibly across chunks) the data described in the coordinates attribute. It
+ * writes those bytes into the supplied buffer at the offset supplied.
+ */
+
+ public void readHeap(byte[] buffer, int bufferOffset, HeapCoordinates coordinates);
+
+ /**
+ * This method reads a single byte of the heap at the given offset.
+ */
+
+ public int readHeap(long offset);
+
+}
\ No newline at end of file
diff --git a/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/heap/HpkHeapReader.java b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/heap/HpkHeapReader.java
new file mode 100644
index 00000000..db96b150
--- /dev/null
+++ b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/heap/HpkHeapReader.java
@@ -0,0 +1,347 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.pkg.heap;
+
+import com.google.common.base.Preconditions;
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
+import org.haikuos.pkg.FileHelper;
+import org.haikuos.pkg.HpkException;
+
+import java.io.Closeable;
+import java.io.File;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.util.zip.DataFormatException;
+import java.util.zip.Inflater;
+
+/**
+ * An instance of this class is able to read the heap's chunks that are in HPK format. Note
+ * that this class will also take responsibility for caching the chunks so that a subsequent
+ * read from the same chunk will not require a re-fault from disk.
+ */
+
+public class HpkHeapReader implements Closeable, HeapReader {
+
+ private File file;
+
+ private HeapCompression compression;
+
+ private long heapOffset;
+
+ private long chunkSize;
+
+ private long compressedSize; // including the shorts for the chunks' compressed sizes
+
+ private long uncompressedSize; // excluding the shorts for the chunks' compressed sizes
+
+ private LoadingCache heapChunkUncompressedCache;
+
+ private int[] heapChunkCompressedLengths = null;
+
+ private RandomAccessFile randomAccessFile;
+
+ private FileHelper fileHelper = new FileHelper();
+
+ public HpkHeapReader(
+ final File file,
+ final HeapCompression compression,
+ final long heapOffset,
+ final long chunkSize,
+ final long compressedSize,
+ final long uncompressedSize) throws HpkException {
+
+ super();
+
+ Preconditions.checkNotNull(file);
+ Preconditions.checkNotNull(compression);
+ Preconditions.checkState(heapOffset > 0 &&heapOffset < Integer.MAX_VALUE);
+ Preconditions.checkState(chunkSize > 0 && chunkSize < Integer.MAX_VALUE);
+ Preconditions.checkState(compressedSize >= 0 && compressedSize < Integer.MAX_VALUE);
+ Preconditions.checkState(uncompressedSize >= 0 && compressedSize < Integer.MAX_VALUE);
+
+ this.file = file;
+ this.compression = compression;
+ this.heapOffset = heapOffset;
+ this.chunkSize = chunkSize;
+ this.compressedSize = compressedSize;
+ this.uncompressedSize = uncompressedSize;
+
+ try {
+ randomAccessFile = new RandomAccessFile(file,"r");
+
+ heapChunkCompressedLengths = new int[getHeapChunkCount()];
+ populateChunkCompressedLengths(heapChunkCompressedLengths);
+
+ heapChunkUncompressedCache = CacheBuilder
+ .newBuilder()
+ .maximumSize(3)
+ .build(new CacheLoader() {
+ @Override
+ public byte[] load(Integer key) throws Exception {
+ Preconditions.checkNotNull(key);
+
+ // TODO: best to avoid continuously allocating new byte buffers
+ byte[] result = new byte[getHeapChunkUncompressedLength(key)];
+ readHeapChunk(key,result);
+ return result;
+ }
+ });
+ }
+ catch(Exception e) {
+ close();
+ throw new HpkException("unable to configure the hpk heap reader",e);
+ }
+ catch(Throwable th) {
+ close();
+ throw new RuntimeException("unable to configure the hkp heap reader",th);
+ }
+
+ }
+
+ @Override
+ public void close() {
+ if(null!=randomAccessFile) {
+ try {
+ randomAccessFile.close();
+ }
+ catch(IOException ioe) {
+ // ignore
+ }
+ }
+ }
+
+ /**
+ * This gives the quantity of chunks that are in the heap.
+ * @return
+ */
+
+ private int getHeapChunkCount() {
+ int count = (int) (uncompressedSize / chunkSize);
+
+ if(0!=uncompressedSize % chunkSize) {
+ count++;
+ }
+
+ return count;
+ }
+
+ private int getHeapChunkUncompressedLength(int index) {
+ if(index < getHeapChunkCount()-1) {
+ return (int) chunkSize;
+ }
+
+ return (int) (uncompressedSize - (chunkSize * (getHeapChunkCount() - 1)));
+ }
+
+ private int getHeapChunkCompressedLength(int index) throws IOException, HpkException {
+ return heapChunkCompressedLengths[index];
+ }
+
+ /**
+ * After the chunk data is a whole lot of unsigned shorts that define the compressed
+ * size of the chunks in the heap. This method will shift the input stream to the
+ * start of those shorts and read them in.
+ */
+
+ private void populateChunkCompressedLengths(int lengths[]) throws IOException, HpkException {
+ Preconditions.checkNotNull(lengths);
+
+ int count = getHeapChunkCount();
+ long totalCompressedLength = 0;
+ randomAccessFile.seek(heapOffset + compressedSize - (2 * (count-1)));
+
+ for(int i=0;i uncompressedSize) {
+ throw new HpkException(
+ String.format("the chunk at %d is of size %d, but the uncompressed length of the chunks is %d",
+ i,
+ lengths[i],
+ uncompressedSize));
+ }
+
+ totalCompressedLength += lengths[i];
+ }
+
+ // the last one will be missing will need to be derived
+ lengths[count-1] = (int) (compressedSize - ((2*(count-1)) + totalCompressedLength));
+
+ if(lengths[count-1] < 0 || lengths[count-1] > uncompressedSize) {
+ throw new HpkException(
+ String.format(
+ "the derivation of the last chunk size of %d is out of bounds",
+ lengths[count-1]));
+ }
+
+ totalCompressedLength += lengths[count-1];
+ }
+
+ private boolean isHeapChunkCompressed(int index) throws IOException, HpkException {
+ return getHeapChunkCompressedLength(index) < getHeapChunkUncompressedLength(index);
+ }
+
+ private long getHeapChunkAbsoluteFileOffset(int index) throws IOException, HpkException {
+ long result = heapOffset; // heap comes after the header.
+
+ for(int i=0;iThis will read from the current offset into the supplied buffer until the supplied buffer is completely
+ * filledup.
+ */
+
+ private void readFully(byte[] buffer) throws IOException, HpkException {
+ Preconditions.checkNotNull(buffer);
+ int total = 0;
+
+ while(total < buffer.length) {
+ int read = randomAccessFile.read(buffer,total,buffer.length - total);
+
+ if(-1==read) {
+ throw new HpkException("unexpected end of file when reading a chunk");
+ }
+
+ total += read;
+ }
+ }
+
+ /**
+ * This will read a chunk of the heap into the supplied buffer. It is assumed that the buffer will be
+ * of the correct length for the uncompressed heap chunk size.
+ */
+
+ private void readHeapChunk(int index, byte[] buffer) throws IOException, HpkException {
+
+ randomAccessFile.seek(getHeapChunkAbsoluteFileOffset(index));
+ int chunkUncompressedLength = getHeapChunkUncompressedLength(index);
+
+ if(isHeapChunkCompressed(index) || HeapCompression.NONE == compression) {
+
+ switch(compression) {
+ case NONE:
+ throw new IllegalStateException();
+
+ case ZLIB:
+ {
+ byte[] deflatedBuffer = new byte[(int) getHeapChunkCompressedLength(index)];
+ readFully(deflatedBuffer);
+
+ Inflater inflater = new Inflater();
+ inflater.setInput(deflatedBuffer);
+
+ try {
+ int read;
+
+ if(chunkUncompressedLength != (read = inflater.inflate(buffer))) {
+
+ // the last chunk size uncompressed may be smaller than the chunk size,
+ // so don't throw an exception if this happens.
+
+ if(index < getHeapChunkCount()-1) {
+ String message = String.format("a compressed heap chunk inflated to %d bytes; was expecting %d",read,chunkUncompressedLength);
+
+ if(inflater.needsInput()) {
+ message += "; needs input";
+ }
+
+ if(inflater.needsDictionary()) {
+ message += "; needs dictionary";
+ }
+
+ throw new HpkException(message);
+ }
+ }
+
+ if(!inflater.finished()) {
+ throw new HpkException(String.format("incomplete inflation of input data while reading chunk %d",index));
+ }
+ }
+ catch(DataFormatException dfe) {
+ throw new HpkException("unable to inflate (decompress) heap chunk "+index,dfe);
+ }
+ }
+ break;
+
+ default:
+ throw new IllegalStateException("unsupported compression; "+compression);
+ }
+ }
+ else {
+ int read;
+
+ if(chunkUncompressedLength != (read = randomAccessFile.read(buffer,0,chunkUncompressedLength))) {
+ throw new HpkException(String.format("problem reading chunk %d of heap; only read %d of %d bytes",index,read,buffer.length));
+ }
+ }
+ }
+
+ @Override
+ public int readHeap(long offset) {
+ Preconditions.checkState(offset >= 0);
+ Preconditions.checkState(offset < uncompressedSize);
+
+ int chunkIndex = (int) (offset / chunkSize);
+ int chunkOffset = (int) (offset - (chunkIndex * chunkSize));
+ byte[] chunkData = heapChunkUncompressedCache.getUnchecked(chunkIndex);
+
+ return chunkData[chunkOffset] & 0xff;
+ }
+
+ @Override
+ public void readHeap(byte[] buffer, int bufferOffset, HeapCoordinates coordinates) {
+
+ Preconditions.checkNotNull(buffer);
+ Preconditions.checkState(bufferOffset >= 0);
+ Preconditions.checkState(bufferOffset < buffer.length);
+ Preconditions.checkState(coordinates.getOffset() >= 0);
+ Preconditions.checkState(coordinates.getOffset() < uncompressedSize);
+ Preconditions.checkState(coordinates.getOffset()+coordinates.getLength() < uncompressedSize);
+
+ // first figure out how much to read from this chunk
+
+ int chunkIndex = (int) (coordinates.getOffset() / chunkSize);
+ int chunkOffset = (int) (coordinates.getOffset() - (chunkIndex * chunkSize));
+ int chunkLength;
+ int chunkUncompressedLength = getHeapChunkUncompressedLength(chunkIndex);
+
+ if(chunkOffset + coordinates.getLength() > chunkUncompressedLength) {
+ chunkLength = (int) (chunkUncompressedLength - chunkOffset);
+ }
+ else {
+ chunkLength = (int) coordinates.getLength();
+ }
+
+ // now read it in.
+
+ byte[] chunkData = heapChunkUncompressedCache.getUnchecked(chunkIndex);
+
+ System.arraycopy(chunkData,chunkOffset,buffer,bufferOffset,chunkLength);
+
+ // if we need to get some more data from the next chunk then call again.
+ // TODO - recursive approach may not be too good when more data is involved; probably ok for hpkr though.
+
+ if(chunkLength < coordinates.getLength()) {
+ readHeap(
+ buffer,
+ bufferOffset + chunkLength,
+ new HeapCoordinates(
+ heapOffset + chunkLength,
+ coordinates.getLength() - chunkLength));
+ }
+
+ }
+
+}
diff --git a/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/model/Attribute.java b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/model/Attribute.java
new file mode 100644
index 00000000..7e5aecac
--- /dev/null
+++ b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/model/Attribute.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.pkg.model;
+
+import com.google.common.base.Optional;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Predicate;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import org.haikuos.pkg.AttributeContext;
+import org.haikuos.pkg.HpkException;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * This is the superclass of the different types (data types) of attributes.
+ */
+
+public abstract class Attribute {
+
+ private AttributeId attributeId;
+
+ private List childAttributes = null;
+
+ public Attribute(AttributeId attributeId) {
+ super();
+ this.attributeId = attributeId;
+ }
+
+ public AttributeId getAttributeId() {
+ return attributeId;
+ }
+
+ public abstract AttributeType getAttributeType();
+
+ public abstract Object getValue(AttributeContext context) throws HpkException;
+
+ public void addChildAttribute(Attribute attribute) {
+ Preconditions.checkNotNull(attribute);
+
+ if(null==childAttributes) {
+ childAttributes = Lists.newArrayList();
+ }
+
+ childAttributes.add(attribute);
+ }
+
+ public boolean hasChildAttributes() {
+ return null!=childAttributes && !childAttributes.isEmpty();
+ }
+
+ public List getChildAttributes() {
+ if(null==childAttributes) {
+ return Collections.emptyList();
+ }
+ return childAttributes;
+ }
+
+ public List getChildAttributes(final AttributeId attributeId) {
+ Preconditions.checkNotNull(attributeId);
+ return Lists.newArrayList(Iterables.filter(
+ getChildAttributes(),
+ new Predicate() {
+ @Override
+ public boolean apply(Attribute input) {
+ return input.getAttributeId() == attributeId;
+ }
+ }
+ ));
+ }
+
+ public Optional getChildAttribute(final AttributeId attributeId) {
+ Preconditions.checkNotNull(attributeId);
+ return Iterables.tryFind(
+ getChildAttributes(),
+ new Predicate() {
+ @Override
+ public boolean apply(Attribute input) {
+ return input.getAttributeId() == attributeId;
+ }
+ }
+ );
+ }
+
+ @Override
+ public String toString() {
+ return getAttributeId().getName() + " : " + getAttributeType().toString();
+ }
+
+}
diff --git a/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/model/AttributeId.java b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/model/AttributeId.java
new file mode 100644
index 00000000..c3339bbb
--- /dev/null
+++ b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/model/AttributeId.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.pkg.model;
+
+/**
+ * These constants define the meaning of an {@link Attribute}. The numerical value is a value that comes up
+ * in the formatted file that maps to these constants. The string value is a name for the attribute and the type
+ * gives the type that is expected to be associated with an {@link Attribute} that has one of these IDs.
+ *
+ * These constants were obtained from
+ * here and then the
+ * search/replace of B_DEFINE_HPKG_ATTRIBUTE\([ ]*(\d+),[ ]*([A-Z]+),[ \t]*("[a-z:\-.]+"),[ \t]*([A-Z_]+)\)
+ * / $4($1,$3,$2),
was applied.
+ */
+
+public enum AttributeId {
+
+ DIRECTORY_ENTRY(0,"dir:entry", AttributeType.STRING),
+ FILE_TYPE(1,"file:type", AttributeType.INT),
+ FILE_PERMISSIONS(2,"file:permissions", AttributeType.INT),
+ FILE_USER(3,"file:user", AttributeType.STRING),
+ FILE_GROUP(4,"file:group", AttributeType.STRING),
+ FILE_ATIME(5,"file:atime", AttributeType.INT),
+ FILE_MTIME(6,"file:mtime", AttributeType.INT),
+ FILE_CRTIME(7,"file:crtime", AttributeType.INT),
+ FILE_ATIME_NANOS(8,"file:atime:nanos", AttributeType.INT),
+ FILE_MTIME_NANOS(9,"file:mtime:nanos", AttributeType.INT),
+ FILE_CRTIM_NANOS(10,"file:crtime:nanos", AttributeType.INT),
+ FILE_ATTRIBUTE(11,"file:attribute", AttributeType.STRING),
+ FILE_ATTRIBUTE_TYPE(12,"file:attribute:type", AttributeType.INT),
+ DATA(13,"data", AttributeType.RAW),
+ SYMLINK_PATH(14,"symlink:path", AttributeType.STRING),
+ PACKAGE_NAME(15,"package:name", AttributeType.STRING),
+ PACKAGE_SUMMARY(16,"package:summary", AttributeType.STRING),
+ PACKAGE_DESCRIPTION(17,"package:description", AttributeType.STRING),
+ PACKAGE_VENDOR(18,"package:vendor", AttributeType.STRING),
+ PACKAGE_PACKAGER(19,"package:packager", AttributeType.STRING),
+ PACKAGE_FLAGS(20,"package:flags", AttributeType.INT),
+ PACKAGE_ARCHITECTURE(21,"package:architecture", AttributeType.INT),
+ PACKAGE_VERSION_MAJOR(22,"package:version.major", AttributeType.STRING),
+ PACKAGE_VERSION_MINOR(23,"package:version.minor", AttributeType.STRING),
+ PACKAGE_VERSION_MICRO(24,"package:version.micro", AttributeType.STRING),
+ PACKAGE_VERSION_REVISION(25,"package:version.revision", AttributeType.INT),
+ PACKAGE_COPYRIGHT(26,"package:copyright", AttributeType.STRING),
+ PACKAGE_LICENSE(27,"package:license", AttributeType.STRING),
+ PACKAGE_PROVIDES(28,"package:provides", AttributeType.STRING),
+ PACKAGE_REQUIRES(29,"package:requires", AttributeType.STRING),
+ PACKAGE_SUPPLEMENTS(30,"package:supplements", AttributeType.STRING),
+ PACKAGE_CONFLICTS(31,"package:conflicts", AttributeType.STRING),
+ PACKAGE_FRESHENS(32,"package:freshens", AttributeType.STRING),
+ PACKAGE_REPLACES(33,"package:replaces", AttributeType.STRING),
+ PACKAGE_RESOLVABLE_OPERATOR(34,"package:resolvable.operator", AttributeType.INT),
+ PACKAGE_CHECKSUM(35,"package:checksum", AttributeType.STRING),
+ PACKAGE_VERSION_PRE_RELEASE(36,"package:version.prerelease", AttributeType.STRING),
+ PACKAGE_PROVIDES_COMPATIBLE(37,"package:provides.compatible", AttributeType.STRING),
+ PACKAGE_URL(38,"package:url", AttributeType.STRING),
+ PACKAGE_SOURCE_URL(39,"package:source-url", AttributeType.STRING),
+ PACKAGE_INSTALL_PATH(40,"package:install-path", AttributeType.STRING),
+ PACKAGE_BASE_PACKAGE(41,"package:base-package", AttributeType.STRING),
+ PACKAGE_GLOBAL_WRITABLE_FILE(42,"package:global-writable-file", AttributeType.STRING),
+ PACKAGE_USER_SETTINGS_FILE(43,"package:user-settings-file", AttributeType.STRING),
+ PACKAGE_WRITABLE_FILE_UPDATE_TYPE(44,"package:writable-file-update-type", AttributeType.INT),
+ PACKAGE_SETTINGS_FILE_TEMPLATE(45,"package:settings-file-template", AttributeType.STRING),
+ PACKAGE_USER(46,"package:user", AttributeType.STRING),
+ PACKAGE_USER_REAL_NAME(47,"package:user.real-name", AttributeType.STRING),
+ PACKAGE_USER_HOME(48,"package:user.home", AttributeType.STRING),
+ PACKAGE_USER_SHELL(49,"package:user.shell", AttributeType.STRING),
+ PACKAGE_USER_GROUP(50,"package:user.group", AttributeType.STRING),
+ PACKAGE_GROUP(51,"package:group", AttributeType.STRING),
+ PACKAGE_POST_INSTALL_SCRIPT(52,"package:post-install-script", AttributeType.STRING),
+ PACKAGE_IS_WRITABLE_DIRECTORY(53,"package:is-writable-directory", AttributeType.INT),
+ PACKAGE(54,"package", AttributeType.STRING);
+
+ private final int code;
+ private final String name;
+ private final AttributeType attributeType;
+
+ AttributeId(int code, String name, AttributeType attributeType) {
+ this.code = code;
+ this.name = name;
+ this.attributeType = attributeType;
+ }
+
+ int getCode() {
+ return code;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public AttributeType getAttributeType() {
+ return attributeType;
+ }
+
+}
diff --git a/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/model/AttributeType.java b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/model/AttributeType.java
new file mode 100644
index 00000000..ab3ad2d9
--- /dev/null
+++ b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/model/AttributeType.java
@@ -0,0 +1,12 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.pkg.model;
+
+public enum AttributeType {
+ INT,
+ STRING,
+ RAW
+}
diff --git a/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/model/IntAttribute.java b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/model/IntAttribute.java
new file mode 100644
index 00000000..d5105850
--- /dev/null
+++ b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/model/IntAttribute.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.pkg.model;
+
+import com.google.common.base.Preconditions;
+import org.haikuos.pkg.AttributeContext;
+import org.haikuos.pkg.HpkException;
+
+import java.math.BigInteger;
+
+/**
+ * This attribute is an integral numeric value. Note that the format specifies either a signed or unsigned value,
+ * but this concrete subclass of @{link Attribute} serves for both the signed and unsigned cases.
+ */
+
+public class IntAttribute extends Attribute {
+
+ private BigInteger numericValue;
+
+ public IntAttribute(AttributeId attributeId, BigInteger numericValue) {
+ super(attributeId);
+ Preconditions.checkNotNull(numericValue);
+ this.numericValue = numericValue;
+ }
+
+ @Override
+ public BigInteger getValue(AttributeContext context) throws HpkException {
+ return numericValue;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ IntAttribute that = (IntAttribute) o;
+
+ if (!numericValue.equals(that.numericValue)) return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return numericValue.hashCode();
+ }
+
+ @Override
+ public AttributeType getAttributeType() {
+ return AttributeType.INT;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("%s : %s",super.toString(),numericValue.toString());
+ }
+
+}
diff --git a/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/model/Pkg.java b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/model/Pkg.java
new file mode 100644
index 00000000..a5ec941b
--- /dev/null
+++ b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/model/Pkg.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.pkg.model;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Lists;
+
+import java.util.Collections;
+import java.util.List;
+
+public class Pkg {
+
+ private String name;
+ private PkgVersion version;
+ private PkgArchitecture architecture;
+ private String vendor;
+ private List copyrights;
+ private List licenses;
+ private String summary;
+ private String description;
+ private PkgUrl homePageUrl;
+
+ public String getDescription() {
+ return description;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ public String getSummary() {
+ return summary;
+ }
+
+ public void setSummary(String summary) {
+ this.summary = summary;
+ }
+
+ public String getVendor() {
+ return vendor;
+ }
+
+ public void setVendor(String vendor) {
+ this.vendor = vendor;
+ }
+
+ public PkgVersion getVersion() {
+ return version;
+ }
+
+ public void setVersion(PkgVersion version) {
+ this.version = version;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public PkgArchitecture getArchitecture() {
+ return architecture;
+ }
+
+ public void setArchitecture(PkgArchitecture architecture) {
+ this.architecture = architecture;
+ }
+
+ public List getCopyrights() {
+ if(null==copyrights) {
+ return Collections.emptyList();
+ }
+ return copyrights;
+ }
+
+ public void addCopyright(String copyright) {
+ Preconditions.checkNotNull(copyright);
+
+ if(null==copyrights) {
+ copyrights = Lists.newArrayList();
+ }
+
+ copyrights.add(copyright);
+ }
+
+ public List getLicenses() {
+ if(null==licenses) {
+ return Collections.emptyList();
+ }
+ return licenses;
+ }
+
+ public void addLicense(String license) {
+ Preconditions.checkNotNull(license);
+
+ if(null==licenses) {
+ licenses = Lists.newArrayList();
+ }
+
+ licenses.add(license);
+ }
+
+ public PkgUrl getHomePageUrl() {
+ return homePageUrl;
+ }
+
+ public void setHomePageUrl(PkgUrl homePageUrl) {
+ this.homePageUrl = homePageUrl;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder stringBuilder = new StringBuilder();
+ stringBuilder.append(null==name?"???":name);
+ stringBuilder.append(" : ");
+ stringBuilder.append(null==version?"???":version.toString());
+ stringBuilder.append(" : ");
+ stringBuilder.append(null==architecture?"???":getArchitecture().toString());
+ return stringBuilder.toString();
+ }
+
+}
diff --git a/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/model/PkgArchitecture.java b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/model/PkgArchitecture.java
new file mode 100644
index 00000000..2bebb88f
--- /dev/null
+++ b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/model/PkgArchitecture.java
@@ -0,0 +1,13 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.pkg.model;
+
+public enum PkgArchitecture {
+ ANY,
+ X86,
+ X86_GCC2,
+ SOURCE
+};
diff --git a/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/model/PkgUrl.java b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/model/PkgUrl.java
new file mode 100644
index 00000000..5ad64bfd
--- /dev/null
+++ b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/model/PkgUrl.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.pkg.model;
+
+import com.google.common.base.Preconditions;
+import com.google.common.base.Strings;
+
+public class PkgUrl {
+
+ private String url;
+
+ private PkgUrlType urlType;
+
+ public PkgUrl(String url, PkgUrlType urlType) {
+ super();
+ Preconditions.checkNotNull(urlType);
+ Preconditions.checkNotNull(url);
+ Preconditions.checkState(!Strings.isNullOrEmpty(url));
+ this.url = url;
+ this.urlType = urlType;
+ }
+
+ public PkgUrlType getUrlType() {
+ return urlType;
+ }
+
+ public String getUrl() {
+ return url;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("%s; %s",urlType.toString(),url);
+ }
+
+}
diff --git a/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/model/PkgUrlType.java b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/model/PkgUrlType.java
new file mode 100644
index 00000000..9d1cee4f
--- /dev/null
+++ b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/model/PkgUrlType.java
@@ -0,0 +1,10 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.pkg.model;
+
+public enum PkgUrlType {
+ HOMEPAGE
+}
diff --git a/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/model/PkgVersion.java b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/model/PkgVersion.java
new file mode 100644
index 00000000..a3731861
--- /dev/null
+++ b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/model/PkgVersion.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.pkg.model;
+
+import com.google.common.base.Preconditions;
+import com.google.common.base.Strings;
+
+public class PkgVersion {
+
+ private String major;
+ private String minor;
+ private String micro;
+ private String preRelease;
+ private Integer revision;
+
+ public PkgVersion(String major, String minor, String micro, String preRelease, Integer revision) {
+ Preconditions.checkState(!Strings.isNullOrEmpty(major));
+ this.major = major;
+ this.minor = minor;
+ this.micro = micro;
+ this.preRelease = preRelease;
+ this.revision = revision;
+ }
+
+ public String getMajor() {
+ return major;
+ }
+
+ public String getMinor() {
+ return minor;
+ }
+
+ public String getMicro() {
+ return micro;
+ }
+
+ public String getPreRelease() {
+ return preRelease;
+ }
+
+ public Integer getRevision() {
+ return revision;
+ }
+
+ private void appendDotValue(StringBuilder stringBuilder, String value) {
+ if(null!=value) {
+ if(0!=stringBuilder.length()) {
+ stringBuilder.append('.');
+ }
+
+ stringBuilder.append(value);
+ }
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder result = new StringBuilder();
+ appendDotValue(result,getMajor());
+ appendDotValue(result,getMinor());
+ appendDotValue(result,getMicro());
+ appendDotValue(result, getPreRelease());
+ appendDotValue(result, null == getRevision() ? null : getRevision().toString());
+ return result.toString();
+ }
+
+}
diff --git a/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/model/RawAttribute.java b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/model/RawAttribute.java
new file mode 100644
index 00000000..82d31e16
--- /dev/null
+++ b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/model/RawAttribute.java
@@ -0,0 +1,14 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.pkg.model;
+
+public abstract class RawAttribute extends Attribute {
+
+ public RawAttribute(AttributeId attributeId) {
+ super(attributeId);
+ }
+
+}
diff --git a/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/model/RawHeapAttribute.java b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/model/RawHeapAttribute.java
new file mode 100644
index 00000000..804fca8a
--- /dev/null
+++ b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/model/RawHeapAttribute.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.pkg.model;
+
+import com.google.common.base.Preconditions;
+import org.haikuos.pkg.AttributeContext;
+import org.haikuos.pkg.HpkException;
+import org.haikuos.pkg.heap.HeapCoordinates;
+
+/**
+ * This type of attribute refers to raw data. It uses coordinates into the heap to provide a source for the
+ * data.
+ */
+
+public class RawHeapAttribute extends RawAttribute {
+
+ private HeapCoordinates heapCoordinates;
+
+ public RawHeapAttribute(AttributeId attributeId, HeapCoordinates heapCoordinates) {
+ super(attributeId);
+ Preconditions.checkNotNull(heapCoordinates);
+ this.heapCoordinates = heapCoordinates;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ RawHeapAttribute that = (RawHeapAttribute) o;
+
+ if (!heapCoordinates.equals(that.heapCoordinates)) return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return heapCoordinates.hashCode();
+ }
+
+ @Override
+ public byte[] getValue(AttributeContext context) throws HpkException {
+ byte[] buffer = new byte[(int) heapCoordinates.getLength()];
+ context.getHeapReader().readHeap(buffer, 0, heapCoordinates);
+ return buffer;
+ }
+
+ @Override
+ public AttributeType getAttributeType() {
+ return AttributeType.RAW;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("%s : @%s",super.toString(),heapCoordinates.toString());
+ }
+
+}
diff --git a/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/model/RawInlineAttribute.java b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/model/RawInlineAttribute.java
new file mode 100644
index 00000000..c66c8970
--- /dev/null
+++ b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/model/RawInlineAttribute.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.pkg.model;
+
+import com.google.common.base.Preconditions;
+import org.haikuos.pkg.AttributeContext;
+import org.haikuos.pkg.HpkException;
+
+import java.util.Arrays;
+
+
+public class RawInlineAttribute extends RawAttribute {
+
+ private byte[] rawValue;
+
+ public RawInlineAttribute(AttributeId attributeId, byte[] rawValue) {
+ super(attributeId);
+ Preconditions.checkNotNull(rawValue);
+ this.rawValue = rawValue;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ RawInlineAttribute that = (RawInlineAttribute) o;
+
+ if (!Arrays.equals(rawValue, that.rawValue)) return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return Arrays.hashCode(rawValue);
+ }
+
+ @Override
+ public byte[] getValue(AttributeContext context) throws HpkException {
+ return rawValue;
+ }
+
+ @Override
+ public AttributeType getAttributeType() {
+ return AttributeType.RAW;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("%s : %d b",super.toString(),rawValue.length);
+ }
+
+}
diff --git a/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/model/StringAttribute.java b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/model/StringAttribute.java
new file mode 100644
index 00000000..6c16336e
--- /dev/null
+++ b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/model/StringAttribute.java
@@ -0,0 +1,14 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.pkg.model;
+
+public abstract class StringAttribute extends Attribute {
+
+ public StringAttribute(AttributeId attributeId) {
+ super(attributeId);
+ }
+
+}
diff --git a/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/model/StringInlineAttribute.java b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/model/StringInlineAttribute.java
new file mode 100644
index 00000000..6555ce82
--- /dev/null
+++ b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/model/StringInlineAttribute.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.pkg.model;
+
+import com.google.common.base.Preconditions;
+import org.haikuos.pkg.AttributeContext;
+import org.haikuos.pkg.HpkException;
+
+/**
+ * This type of attribute is a string. The string is supplied in the stream of attributes so this attribute will
+ * carry the string.
+ */
+
+public class StringInlineAttribute extends StringAttribute {
+
+ private String stringValue;
+
+ public StringInlineAttribute(AttributeId attributeId, String stringValue) {
+ super(attributeId);
+ Preconditions.checkNotNull(stringValue);
+ this.stringValue = stringValue;
+ }
+
+ @Override
+ public String getValue(AttributeContext context) throws HpkException {
+ return stringValue;
+ }
+
+ @Override
+ public AttributeType getAttributeType() {
+ return AttributeType.STRING;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ StringInlineAttribute that = (StringInlineAttribute) o;
+
+ if (!stringValue.equals(that.stringValue)) return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return stringValue.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return String.format("%s : %s",super.toString(),stringValue.toString());
+ }
+
+}
diff --git a/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/model/StringTableRefAttribute.java b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/model/StringTableRefAttribute.java
new file mode 100644
index 00000000..1fe017ae
--- /dev/null
+++ b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/model/StringTableRefAttribute.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.pkg.model;
+
+import com.google.common.base.Preconditions;
+import org.haikuos.pkg.AttributeContext;
+import org.haikuos.pkg.HpkException;
+
+/**
+ * This type of attribute references a string from an instance of {@link org.haikuos.pkg.HpkStringTable}
+ * which is typically obtained from an instance of {@link org.haikuos.pkg.AttributeContext}.
+ */
+
+public class StringTableRefAttribute extends StringAttribute {
+
+ private int index;
+
+ public StringTableRefAttribute(AttributeId attributeId, int index) {
+ super(attributeId);
+ Preconditions.checkState(index >= 0);
+ this.index = index;
+ }
+
+ @Override
+ public String getValue(AttributeContext context) throws HpkException {
+ return context.getStringTable().getString(index);
+ }
+
+ @Override
+ public AttributeType getAttributeType() {
+ return AttributeType.STRING;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ StringTableRefAttribute that = (StringTableRefAttribute) o;
+
+ if (index != that.index) return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return index;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("%s : @%d",super.toString(),index);
+ }
+
+}
diff --git a/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/output/AttributeWriter.java b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/output/AttributeWriter.java
new file mode 100644
index 00000000..9cd28928
--- /dev/null
+++ b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/output/AttributeWriter.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.pkg.output;
+
+import com.google.common.base.Preconditions;
+import org.haikuos.pkg.AttributeContext;
+import org.haikuos.pkg.AttributeIterator;
+import org.haikuos.pkg.HpkException;
+import org.haikuos.pkg.model.Attribute;
+
+import java.io.FilterWriter;
+import java.io.IOException;
+import java.io.Writer;
+
+/**
+ * This is a writer in the sense that it writes a human-readable dump of the attributes to a {@link Writer}. This
+ * enables debug or diagnostic output to be written to, for example, a file or standard output. It will write the
+ * attributes in an indented tree structure.
+ */
+
+public class AttributeWriter extends FilterWriter {
+
+ public AttributeWriter(Writer writer) {
+ super(writer);
+ }
+
+ private void write(int indent, AttributeContext context, Attribute attribute) throws IOException {
+ Preconditions.checkNotNull(context);
+ Preconditions.checkNotNull(attribute);
+ Preconditions.checkState(indent >= 0);
+
+ for(int i=0;iThis is a writer in the sense of being able to produce a human-readable output of a package.
+ */
+
+public class PkgWriter extends FilterWriter {
+
+ public PkgWriter(Writer writer) {
+ super(writer);
+ }
+
+ private void write(Pkg pkg) throws IOException {
+ Preconditions.checkNotNull(pkg);
+ write(pkg.toString());
+ }
+
+ public void write(PkgIterator pkgIterator) throws IOException, HpkException {
+ Preconditions.checkNotNull(pkgIterator);
+
+ try {
+ while(pkgIterator.hasNext()) {
+ write(pkgIterator.next());
+ write('\n');
+ }
+ }
+ catch(PkgException pe) {
+ throw new IOException("unable to write a package owing to an exception obtaining the package",pe);
+ }
+ }
+
+}
diff --git a/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/package-info.java b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/package-info.java
new file mode 100644
index 00000000..5aa0b698
--- /dev/null
+++ b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/package-info.java
@@ -0,0 +1,11 @@
+/**
+ * This package contains controller, model and helper objects for reading package files for the
+ * HaikuOS project. Pkg files come in two types. HPKR is a file
+ * format for providing a kind of catalogue of what is in a repository. HPKG format is a file that describes
+ * a particular package. At the time of writing, this library only supports HPKR although there is enough
+ * supporting material to easily provide a reader for HPKG.
+ *
+ * Note that this library (currently) only supports (signed) 32bit addressing in the HPKR files.
+ */
+
+package org.haikuos.pkg;
\ No newline at end of file
diff --git a/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/tool/AttributeDumpTool.java b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/tool/AttributeDumpTool.java
new file mode 100644
index 00000000..810dc1de
--- /dev/null
+++ b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/tool/AttributeDumpTool.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.pkg.tool;
+
+import org.haikuos.pkg.HpkException;
+import org.haikuos.pkg.HpkrFileExtractor;
+import org.haikuos.pkg.output.AttributeWriter;
+import org.kohsuke.args4j.CmdLineException;
+import org.kohsuke.args4j.CmdLineParser;
+import org.kohsuke.args4j.Option;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+
+/**
+ * Given an HPKR file, this small program will dump all of the attributes of the HPKR file. This is handy for
+ * diagnostic purposes.
+ */
+
+public class AttributeDumpTool implements Runnable {
+
+ protected static Logger logger = LoggerFactory.getLogger(AttributeDumpTool.class);
+
+ @Option(name = "-f", required = true, usage = "the HPKR file is required")
+ private File hpkrFile;
+
+ public static void main(String[] args) throws HpkException, IOException {
+ AttributeDumpTool main = new AttributeDumpTool();
+ CmdLineParser parser = new CmdLineParser(main);
+
+ try {
+ parser.parseArgument(args);
+ main.run();
+ }
+ catch(CmdLineException cle) {
+ throw new IllegalStateException("unable to parse arguments",cle);
+ }
+ }
+
+ public void run() {
+ CmdLineParser parser = new CmdLineParser(this);
+
+ HpkrFileExtractor hpkrFileExtractor = null;
+
+ try {
+ hpkrFileExtractor = new HpkrFileExtractor(hpkrFile);
+
+ OutputStreamWriter streamWriter = new OutputStreamWriter(System.out);
+ AttributeWriter attributeWriter = new AttributeWriter(streamWriter);
+ attributeWriter.write(hpkrFileExtractor.getPackageAttributesIterator());
+ attributeWriter.flush();
+ }
+ catch(Throwable th) {
+ logger.error("unable to dump attributes",th);
+ }
+ finally {
+ if(null!=hpkrFileExtractor) {
+ hpkrFileExtractor.close();
+ }
+ }
+
+ }
+
+}
diff --git a/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/tool/PkgDumpTool.java b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/tool/PkgDumpTool.java
new file mode 100644
index 00000000..3d9d2e45
--- /dev/null
+++ b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/tool/PkgDumpTool.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.pkg.tool;
+
+import org.haikuos.pkg.HpkException;
+import org.haikuos.pkg.HpkrFileExtractor;
+import org.haikuos.pkg.PkgIterator;
+import org.haikuos.pkg.output.PkgWriter;
+import org.kohsuke.args4j.CmdLineException;
+import org.kohsuke.args4j.CmdLineParser;
+import org.kohsuke.args4j.Option;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+
+/**
+ * This small tool will take an HPKR file and parse it first into attributes and then into packages. The packages
+ * are dumped out to the standard output. This is useful as means of debugging.
+ */
+
+public class PkgDumpTool {
+
+ protected static Logger logger = LoggerFactory.getLogger(AttributeDumpTool.class);
+
+ @Option(name = "-f", required = true, usage = "the HPKR file is required")
+ private File hpkrFile;
+
+ public static void main(String[] args) throws HpkException, IOException {
+ PkgDumpTool main = new PkgDumpTool();
+ CmdLineParser parser = new CmdLineParser(main);
+
+ try {
+ parser.parseArgument(args);
+ main.run();
+ }
+ catch(CmdLineException cle) {
+ throw new IllegalStateException("unable to parse arguments",cle);
+ }
+ }
+
+ public void run() {
+ CmdLineParser parser = new CmdLineParser(this);
+
+ HpkrFileExtractor hpkrFileExtractor = null;
+
+ try {
+ hpkrFileExtractor = new HpkrFileExtractor(hpkrFile);
+
+ OutputStreamWriter streamWriter = new OutputStreamWriter(System.out);
+ PkgWriter pkgWriter = new PkgWriter(streamWriter);
+ pkgWriter.write(new PkgIterator(hpkrFileExtractor.getPackageAttributesIterator()));
+ pkgWriter.flush();
+ }
+ catch(Throwable th) {
+ logger.error("unable to dump packages",th);
+ }
+ finally {
+ if(null!=hpkrFileExtractor) {
+ hpkrFileExtractor.close();
+ }
+ }
+
+ }
+
+
+
+}
diff --git a/haikudepotserver-packagefile/src/test/java/org/haikuos/pkg/AbstractHpkrTest.java b/haikudepotserver-packagefile/src/test/java/org/haikuos/pkg/AbstractHpkrTest.java
new file mode 100644
index 00000000..0ea37f35
--- /dev/null
+++ b/haikudepotserver-packagefile/src/test/java/org/haikuos/pkg/AbstractHpkrTest.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.pkg;
+
+import com.google.common.io.ByteStreams;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+public abstract class AbstractHpkrTest {
+
+ /**
+ * This will copy the supplied test classpath resource into a temporary file to work with during the test. It
+ * is the responsibility of the caller to clean up the temporary file afterwards.
+ */
+
+ protected File prepareTestFile() throws IOException {
+ InputStream inputStream = getClass().getResourceAsStream("/repo.hpkr");
+
+ if(null==inputStream) {
+ throw new IllegalStateException("unable to find the test hpkr resource");
+ }
+
+ File temporaryFile = File.createTempFile("repo-test-","hpkr");
+ FileOutputStream fileOutputStream = null;
+
+ try {
+ fileOutputStream = new FileOutputStream(temporaryFile);
+ ByteStreams.copy(inputStream, fileOutputStream);
+ }
+ catch(IOException ioe) {
+ temporaryFile.delete();
+ throw new IOException("unable to copy the test hpkr resource to a temporary file; "+temporaryFile.getAbsolutePath());
+ }
+ finally {
+ if(null!=fileOutputStream) {
+ try {
+ fileOutputStream.close();
+ }
+ catch(IOException ioe) {
+ // ignore
+ }
+ }
+ }
+
+ return temporaryFile;
+ }
+
+}
diff --git a/haikudepotserver-packagefile/src/test/java/org/haikuos/pkg/HpkrFileExtractorAttributeTest.java b/haikudepotserver-packagefile/src/test/java/org/haikuos/pkg/HpkrFileExtractorAttributeTest.java
new file mode 100644
index 00000000..ff2a89cd
--- /dev/null
+++ b/haikudepotserver-packagefile/src/test/java/org/haikuos/pkg/HpkrFileExtractorAttributeTest.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.pkg;
+
+import org.haikuos.pkg.model.Attribute;
+import org.haikuos.pkg.model.AttributeId;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.OutputStreamWriter;
+import java.math.BigInteger;
+
+import static org.fest.assertions.Assertions.assertThat;
+
+/**
+ * This is a simplistic test that is just going to stream out some attributes from a known HPKR file and will
+ * then look for certain packages and make sure that an artificially created attribute set matches. It's basically
+ * a smoke test.
+ */
+
+public class HpkrFileExtractorAttributeTest extends AbstractHpkrTest {
+
+ @Test
+ public void testRepo() throws Exception {
+
+ File hpkrFile = null;
+ HpkrFileExtractor hpkrFileExtractor = null;
+ Attribute ncursesSourceAttribute = null;
+
+ try {
+ hpkrFile = prepareTestFile();
+ hpkrFileExtractor = new HpkrFileExtractor(hpkrFile);
+
+ OutputStreamWriter streamWriter = new OutputStreamWriter(System.out);
+ AttributeIterator attributeIterator = hpkrFileExtractor.getPackageAttributesIterator();
+ AttributeContext attributeContext = hpkrFileExtractor.getAttributeContext();
+
+ while(attributeIterator.hasNext()) {
+ Attribute attribute = attributeIterator.next();
+
+ if(AttributeId.PACKAGE == attribute.getAttributeId()) {
+ String packageName = attribute.getValue(attributeIterator.getContext()).toString();
+
+ if(packageName.equals("ncurses_source")) {
+ ncursesSourceAttribute = attribute;
+ }
+ }
+ }
+
+ // now the analysis phase.
+
+ assertThat(ncursesSourceAttribute).isNotNull();
+ assertThat(ncursesSourceAttribute.getChildAttribute(AttributeId.PACKAGE_NAME).get().getValue(attributeContext)).isEqualTo("ncurses_source");
+ assertThat(ncursesSourceAttribute.getChildAttribute(AttributeId.PACKAGE_ARCHITECTURE).get().getValue(attributeContext)).isEqualTo(new BigInteger("3"));
+ assertThat(ncursesSourceAttribute.getChildAttribute(AttributeId.PACKAGE_URL).get().getValue(attributeContext)).isEqualTo("http://www.gnu.org/software/ncurses/ncurses.html");
+ assertThat(ncursesSourceAttribute.getChildAttribute(AttributeId.PACKAGE_SOURCE_URL).get().getValue(attributeContext)).isEqualTo("Download ");
+ assertThat(ncursesSourceAttribute.getChildAttribute(AttributeId.PACKAGE_CHECKSUM).get().getValue(attributeContext)).isEqualTo("6a25c52890e7d335247bd96965b5cac2f04dafc1de8d12ad73346ed79f3f4215");
+
+ // check the version which is a sub-tree of attributes.
+
+ Attribute majorVersionAttribute = ncursesSourceAttribute.getChildAttribute(AttributeId.PACKAGE_VERSION_MAJOR).get();
+ assertThat(majorVersionAttribute.getValue(attributeContext)).isEqualTo("5");
+ assertThat(majorVersionAttribute.getChildAttribute(AttributeId.PACKAGE_VERSION_MINOR).get().getValue(attributeContext)).isEqualTo("9");
+ assertThat(majorVersionAttribute.getChildAttribute(AttributeId.PACKAGE_VERSION_REVISION).get().getValue(attributeContext)).isEqualTo(new BigInteger("10"));
+
+ }
+ finally {
+ if(null!=hpkrFileExtractor) {
+ hpkrFileExtractor.close();
+ }
+
+ if(null!=hpkrFile) {
+ hpkrFile.delete();
+ }
+ }
+
+ }
+
+}
diff --git a/haikudepotserver-packagefile/src/test/java/org/haikuos/pkg/PkgFactoryTest.java b/haikudepotserver-packagefile/src/test/java/org/haikuos/pkg/PkgFactoryTest.java
new file mode 100644
index 00000000..0625451f
--- /dev/null
+++ b/haikudepotserver-packagefile/src/test/java/org/haikuos/pkg/PkgFactoryTest.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.pkg;
+
+import com.google.common.base.Predicate;
+import com.google.common.collect.Iterables;
+import org.haikuos.pkg.model.*;
+import org.junit.Test;
+
+import java.math.BigInteger;
+
+import static org.fest.assertions.Assertions.assertThat;
+
+/**
+ * This is a very simplistic test that checks that attributes are able to be converted into package DTO model
+ * objects correctly.
+ */
+
+public class PkgFactoryTest {
+
+ private Attribute createTestPackageAttributes() {
+
+ StringInlineAttribute topA = new StringInlineAttribute(AttributeId.PACKAGE,"testpkg");
+ topA.addChildAttribute(new StringInlineAttribute(AttributeId.PACKAGE_NAME,"testpkg"));
+ topA.addChildAttribute(new StringInlineAttribute(AttributeId.PACKAGE_VENDOR,"Test Vendor"));
+ topA.addChildAttribute(new StringInlineAttribute(AttributeId.PACKAGE_SUMMARY,"This is a test package summary"));
+ topA.addChildAttribute(new StringInlineAttribute(AttributeId.PACKAGE_DESCRIPTION,"This is a test package description"));
+ topA.addChildAttribute(new StringInlineAttribute(AttributeId.PACKAGE_URL,"http://www.haiku-os.org"));
+ topA.addChildAttribute(new IntAttribute(AttributeId.PACKAGE_ARCHITECTURE,new BigInteger("1"))); // X86
+
+ StringInlineAttribute majorVersionA = new StringInlineAttribute(AttributeId.PACKAGE_VERSION_MAJOR,"6");
+ majorVersionA.addChildAttribute(new StringInlineAttribute(AttributeId.PACKAGE_VERSION_MINOR,"32"));
+ majorVersionA.addChildAttribute(new StringInlineAttribute(AttributeId.PACKAGE_VERSION_MICRO,"9"));
+ majorVersionA.addChildAttribute(new StringInlineAttribute(AttributeId.PACKAGE_VERSION_PRE_RELEASE,"beta"));
+ majorVersionA.addChildAttribute(new IntAttribute(AttributeId.PACKAGE_VERSION_REVISION,new BigInteger("8")));
+ topA.addChildAttribute(majorVersionA);
+
+ topA.addChildAttribute(new StringInlineAttribute(AttributeId.PACKAGE_COPYRIGHT,"Some copyright A"));
+ topA.addChildAttribute(new StringInlineAttribute(AttributeId.PACKAGE_COPYRIGHT,"Some copyright B"));
+ topA.addChildAttribute(new StringInlineAttribute(AttributeId.PACKAGE_LICENSE,"Some license A"));
+ topA.addChildAttribute(new StringInlineAttribute(AttributeId.PACKAGE_LICENSE,"Some license B"));
+
+ return topA;
+ }
+
+ @Test
+ public void testCreatePackage() throws PkgException {
+
+ Attribute attribute = createTestPackageAttributes();
+ PkgFactory pkgFactory = new PkgFactory();
+
+ // it is ok that this is empty because the factory should not need to reference the heap again; its all inline
+ // and we are not testing the heap here.
+ AttributeContext attributeContext = new AttributeContext();
+
+ Pkg pkg = pkgFactory.createPackage(
+ attributeContext,
+ attribute);
+
+ // now do some checks.
+
+ assertThat(pkg.getArchitecture()).isEqualTo(PkgArchitecture.X86);
+ assertThat(pkg.getName()).isEqualTo("testpkg");
+ assertThat(pkg.getVendor()).isEqualTo("Test Vendor");
+ assertThat(pkg.getSummary()).isEqualTo("This is a test package summary");
+ assertThat(pkg.getDescription()).isEqualTo("This is a test package description");
+ assertThat(pkg.getHomePageUrl().getUrl()).isEqualTo("http://www.haiku-os.org");
+ assertThat(pkg.getHomePageUrl().getUrlType()).isEqualTo(PkgUrlType.HOMEPAGE);
+
+ assertThat(pkg.getVersion().getMajor()).isEqualTo("6");
+ assertThat(pkg.getVersion().getMinor()).isEqualTo("32");
+ assertThat(pkg.getVersion().getMicro()).isEqualTo("9");
+ assertThat(pkg.getVersion().getPreRelease()).isEqualTo("beta");
+ assertThat(pkg.getVersion().getRevision()).isEqualTo(8);
+
+ assertThat(Iterables.tryFind(
+ pkg.getCopyrights(),
+ new Predicate() {
+ @Override
+ public boolean apply(java.lang.String input) {
+ return input.equals("Some copyright A");
+ }
+ }).isPresent()).isTrue();
+
+ assertThat(Iterables.tryFind(
+ pkg.getCopyrights(),
+ new Predicate() {
+ @Override
+ public boolean apply(java.lang.String input) {
+ return input.equals("Some copyright B");
+ }
+ }).isPresent()).isTrue();
+
+ assertThat(Iterables.tryFind(
+ pkg.getLicenses(),
+ new Predicate() {
+ @Override
+ public boolean apply(java.lang.String input) {
+ return input.equals("Some license A");
+ }
+ }).isPresent()).isTrue();
+
+ assertThat(Iterables.tryFind(
+ pkg.getLicenses(),
+ new Predicate() {
+ @Override
+ public boolean apply(java.lang.String input) {
+ return input.equals("Some license B");
+ }
+ }).isPresent()).isTrue();
+
+ }
+
+}
diff --git a/haikudepotserver-packagefile/src/test/resources/README.TXT b/haikudepotserver-packagefile/src/test/resources/README.TXT
new file mode 100644
index 00000000..ca8c31c0
--- /dev/null
+++ b/haikudepotserver-packagefile/src/test/resources/README.TXT
@@ -0,0 +1,5 @@
+The file "repo.hpkr" was obtained from;
+
+http://haiku-files.org/files/repo/9818164862edcbf69404a90267090b9d595908a11941951e904dfc6244c3d566/repo
+
+2013-09-30
\ No newline at end of file
diff --git a/haikudepotserver-packagefile/src/test/resources/repo.hpkr b/haikudepotserver-packagefile/src/test/resources/repo.hpkr
new file mode 100644
index 0000000000000000000000000000000000000000..62b89933970ac94eb7fdce8ab2bb16aefb4c4e54
GIT binary patch
literal 48997
zcmV(~K+nHuaBFe^NB{x=000000Ka7b000310RR91000000KXjo000000stle00GSa
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