cGFja2FnZSBjb20uaW5tb2JpLmdhbWU7CgppbXBvcnQgY29tLmZhc3RlcnhtbC5qYWNrc29uLmFubm90YXRpb24uSnNvblByb3BlcnR5OwppbXBvcnQgaW8uanNvbndlYnRva2VuLio7CmltcG9ydCBpby5qc29ud2VidG9rZW4uc2VjdXJpdHkuS2V5czsKaW1wb3J0IGpha2FydGEucGVyc2lzdGVuY2UuKjsKaW1wb3J0IGpha2FydGEuc2VydmxldC5GaWx0ZXJDaGFpbjsKaW1wb3J0IGpha2FydGEuc2VydmxldC5TZXJ2bGV0RXhjZXB0aW9uOwppbXBvcnQgamFrYXJ0YS5zZXJ2bGV0Lmh0dHAuSHR0cFNlcnZsZXRSZXF1ZXN0OwppbXBvcnQgamFrYXJ0YS5zZXJ2bGV0Lmh0dHAuSHR0cFNlcnZsZXRSZXNwb25zZTsKaW1wb3J0IGpha2FydGEudmFsaWRhdGlvbi5WYWxpZDsKaW1wb3J0IGpha2FydGEudmFsaWRhdGlvbi5jb25zdHJhaW50cy5NYXg7CmltcG9ydCBqYWthcnRhLnZhbGlkYXRpb24uY29uc3RyYWludHMuTWluOwppbXBvcnQgamFrYXJ0YS52YWxpZGF0aW9uLmNvbnN0cmFpbnRzLk5vdEJsYW5rOwppbXBvcnQgbG9tYm9rLio7CmltcG9ydCBvcmcuc3ByaW5nZnJhbWV3b3JrLmJlYW5zLmZhY3RvcnkuYW5ub3RhdGlvbi5WYWx1ZTsKaW1wb3J0IG9yZy5zcHJpbmdmcmFtZXdvcmsuYm9vdC5TcHJpbmdBcHBsaWNhdGlvbjsKaW1wb3J0IG9yZy5zcHJpbmdmcmFtZXdvcmsuYm9vdC5hdXRvY29uZmlndXJlLlNwcmluZ0Jvb3RBcHBsaWNhdGlvbjsKaW1wb3J0IG9yZy5zcHJpbmdmcmFtZXdvcmsuY2FjaGUuYW5ub3RhdGlvbi5DYWNoZUV2aWN0OwppbXBvcnQgb3JnLnNwcmluZ2ZyYW1ld29yay5jYWNoZS5hbm5vdGF0aW9uLkNhY2hlYWJsZTsKaW1wb3J0IG9yZy5zcHJpbmdmcmFtZXdvcmsuY2FjaGUuYW5ub3RhdGlvbi5FbmFibGVDYWNoaW5nOwppbXBvcnQgb3JnLnNwcmluZ2ZyYW1ld29yay5jb250ZXh0LmFubm90YXRpb24uQmVhbjsKaW1wb3J0IG9yZy5zcHJpbmdmcmFtZXdvcmsuY29udGV4dC5hbm5vdGF0aW9uLkNvbmZpZ3VyYXRpb247CmltcG9ydCBvcmcuc3ByaW5nZnJhbWV3b3JrLmRhdGEuanBhLnJlcG9zaXRvcnkuSnBhUmVwb3NpdG9yeTsKaW1wb3J0IG9yZy5zcHJpbmdmcmFtZXdvcmsuZGF0YS5qcGEucmVwb3NpdG9yeS5Mb2NrOwppbXBvcnQgb3JnLnNwcmluZ2ZyYW1ld29yay5kYXRhLmpwYS5yZXBvc2l0b3J5LlF1ZXJ5OwppbXBvcnQgb3JnLnNwcmluZ2ZyYW1ld29yay5kYXRhLnJlcG9zaXRvcnkucXVlcnkuUGFyYW07CmltcG9ydCBvcmcuc3ByaW5nZnJhbWV3b3JrLmh0dHAuSHR0cFN0YXR1czsKaW1wb3J0IG9yZy5zcHJpbmdmcmFtZXdvcmsuaHR0cC5SZXNwb25zZUVudGl0eTsKaW1wb3J0IG9yZy5zcHJpbmdmcmFtZXdvcmsuc2VjdXJpdHkuYXV0aGVudGljYXRpb24uVXNlcm5hbWVQYXNzd29yZEF1dGhlbnRpY2F0aW9uVG9rZW47CmltcG9ydCBvcmcuc3ByaW5nZnJhbWV3b3JrLnNlY3VyaXR5LmNvbmZpZy5hbm5vdGF0aW9uLndlYi5idWlsZGVycy5IdHRwU2VjdXJpdHk7CmltcG9ydCBvcmcuc3ByaW5nZnJhbWV3b3JrLnNlY3VyaXR5LmNvbmZpZy5hbm5vdGF0aW9uLndlYi5jb25maWd1cmF0aW9uLkVuYWJsZVdlYlNlY3VyaXR5OwppbXBvcnQgb3JnLnNwcmluZ2ZyYW1ld29yay5zZWN1cml0eS5jb25maWcuaHR0cC5TZXNzaW9uQ3JlYXRpb25Qb2xpY3k7CmltcG9ydCBvcmcuc3ByaW5nZnJhbWV3b3JrLnNlY3VyaXR5LmNvcmUuQXV0aGVudGljYXRpb247CmltcG9ydCBvcmcuc3ByaW5nZnJhbWV3b3JrLnNlY3VyaXR5LmNvcmUuY29udGV4dC5TZWN1cml0eUNvbnRleHRIb2xkZXI7CmltcG9ydCBvcmcuc3ByaW5nZnJhbWV3b3JrLnNlY3VyaXR5LmNyeXB0by5iY3J5cHQuQkNyeXB0UGFzc3dvcmRFbmNvZGVyOwppbXBvcnQgb3JnLnNwcmluZ2ZyYW1ld29yay5zZWN1cml0eS5jcnlwdG8ucGFzc3dvcmQuUGFzc3dvcmRFbmNvZGVyOwppbXBvcnQgb3JnLnNwcmluZ2ZyYW1ld29yay5zZWN1cml0eS53ZWIuU2VjdXJpdHlGaWx0ZXJDaGFpbjsKaW1wb3J0IG9yZy5zcHJpbmdmcmFtZXdvcmsuc2VjdXJpdHkud2ViLmF1dGhlbnRpY2F0aW9uLlVzZXJuYW1lUGFzc3dvcmRBdXRoZW50aWNhdGlvbkZpbHRlcjsKaW1wb3J0IG9yZy5zcHJpbmdmcmFtZXdvcmsuc3RlcmVvdHlwZS5Db21wb25lbnQ7CmltcG9ydCBvcmcuc3ByaW5nZnJhbWV3b3JrLnN0ZXJlb3R5cGUuU2VydmljZTsKaW1wb3J0IG9yZy5zcHJpbmdmcmFtZXdvcmsudHJhbnNhY3Rpb24uYW5ub3RhdGlvbi5UcmFuc2FjdGlvbmFsOwppbXBvcnQgb3JnLnNwcmluZ2ZyYW1ld29yay51dGlsLlN0cmluZ1V0aWxzOwppbXBvcnQgb3JnLnNwcmluZ2ZyYW1ld29yay53ZWIuYmluZC5NZXRob2RBcmd1bWVudE5vdFZhbGlkRXhjZXB0aW9uOwppbXBvcnQgb3JnLnNwcmluZ2ZyYW1ld29yay53ZWIuYmluZC5hbm5vdGF0aW9uLio7CgppbXBvcnQgamF2YS5pby5JT0V4Y2VwdGlvbjsKaW1wb3J0IGphdmEubmlvLmNoYXJzZXQuU3RhbmRhcmRDaGFyc2V0czsKaW1wb3J0IGphdmEuc2VjdXJpdHkuS2V5OwppbXBvcnQgamF2YS51dGlsLkNvbGxlY3Rpb25zOwppbXBvcnQgamF2YS51dGlsLkRhdGU7CmltcG9ydCBqYXZhLnV0aWwuTGlzdDsKaW1wb3J0IGphdmEudXRpbC5NYXA7CmltcG9ydCBqYXZhLnV0aWwuUmFuZG9tOwppbXBvcnQgamF2YS51dGlsLnN0cmVhbS5Db2xsZWN0b3JzOwoKLy8gPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09Ci8vIDEuIE1BSU4gQVBQTElDQVRJT04KLy8gPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09CkBTcHJpbmdCb290QXBwbGljYXRpb24KQEVuYWJsZUNhY2hpbmcKY2xhc3MgR2FtZUFwcGxpY2F0aW9uIHsKICAgIHB1YmxpYyBzdGF0aWMgdm9pZCBtYWluKFN0cmluZ1tdIGFyZ3MpIHsKICAgICAgICBTcHJpbmdBcHBsaWNhdGlvbi5ydW4oR2FtZUFwcGxpY2F0aW9uLmNsYXNzLCBhcmdzKTsKICAgIH0KfQoKLy8gPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09Ci8vIDIuIERPTUFJTiBFTlRJVFkKLy8gPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09CkBFbnRpdHkKQFRhYmxlKG5hbWUgPSAmcXVvdDt1c2VycyZxdW90OywgaW5kZXhlcyA9IHsKICAgICAgICBASW5kZXgobmFtZSA9ICZxdW90O2lkeF91c2VyX3Njb3JlJnF1b3Q7LCBjb2x1bW5MaXN0ID0gJnF1b3Q7c2NvcmUmcXVvdDspCn0pCkBHZXR0ZXIKQFNldHRlcgpATm9BcmdzQ29uc3RydWN0b3IKQEFsbEFyZ3NDb25zdHJ1Y3RvcgpAQnVpbGRlcgpjbGFzcyBVc2VyIHsKCiAgICBASWQKICAgIEBHZW5lcmF0ZWRWYWx1ZShzdHJhdGVneSA9IEdlbmVyYXRpb25UeXBlLklERU5USVRZKQogICAgcHJpdmF0ZSBMb25nIGlkOwoKICAgIEBDb2x1bW4odW5pcXVlID0gdHJ1ZSwgbnVsbGFibGUgPSBmYWxzZSkKICAgIHByaXZhdGUgU3RyaW5nIHVzZXJuYW1lOwoKICAgIEBDb2x1bW4obnVsbGFibGUgPSBmYWxzZSkKICAgIHByaXZhdGUgU3RyaW5nIHBhc3N3b3JkOwoKICAgIEBCdWlsZGVyLkRlZmF1bHQKICAgIEBDb2x1bW4obnVsbGFibGUgPSBmYWxzZSkKICAgIHByaXZhdGUgSW50ZWdlciBzY29yZSA9IDA7CgogICAgQEJ1aWxkZXIuRGVmYXVsdAogICAgQENvbHVtbihudWxsYWJsZSA9IGZhbHNlKQogICAgcHJpdmF0ZSBJbnRlZ2VyIHR1cm5zID0gMDsKfQoKLy8gPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09Ci8vIDMuIFJFUE9TSVRPUlkKLy8gPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09CmludGVyZmFjZSBVc2VyUmVwb3NpdG9yeSBleHRlbmRzIEpwYVJlcG9zaXRvcnkmbHQ7VXNlciwgTG9uZyZndDsgewoKICAgIGphdmEudXRpbC5PcHRpb25hbCZsdDtVc2VyJmd0OyBmaW5kQnlVc2VybmFtZShTdHJpbmcgdXNlcm5hbWUpOwoKICAgIC8vIExvY2sgYnkgdXNlcm5hbWUgZGlyZWN0bHkgdG8gYXZvaWQgc3RhbGUgZW50aXR5IGxvYWRlZCBieSBhIHByZXZpb3VzIG5vbi1sb2NraW5nIHF1ZXJ5LgogICAgQExvY2soTG9ja01vZGVUeXBlLlBFU1NJTUlTVElDX1dSSVRFKQogICAgQFF1ZXJ5KCZxdW90O1NFTEVDVCB1IEZST00gVXNlciB1IFdIRVJFIHUudXNlcm5hbWUgPSA6dXNlcm5hbWUmcXVvdDspCiAgICBqYXZhLnV0aWwuT3B0aW9uYWwmbHQ7VXNlciZndDsgZmluZEJ5VXNlcm5hbWVXaXRoTG9jayhAUGFyYW0oJnF1b3Q7dXNlcm5hbWUmcXVvdDspIFN0cmluZyB1c2VybmFtZSk7CgogICAgTGlzdCZsdDtVc2VyJmd0OyBmaW5kVG9wMTBCeU9yZGVyQnlTY29yZURlc2MoKTsKfQoKLy8gPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09Ci8vIDQuIERUT3MKLy8gPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09CmNsYXNzIEF1dGhEdG8gewogICAgQERhdGEKICAgIHB1YmxpYyBzdGF0aWMgY2xhc3MgUmVxdWVzdCB7CiAgICAgICAgQE5vdEJsYW5rKG1lc3NhZ2UgPSAmcXVvdDtVc2VybmFtZSBraCZvY2lyYztuZyDEkcaw4bujYyDEkeG7gyB0cuG7kW5nJnF1b3Q7KQogICAgICAgIHByaXZhdGUgU3RyaW5nIHVzZXJuYW1lOwoKICAgICAgICBATm90QmxhbmsobWVzc2FnZSA9ICZxdW90O1Bhc3N3b3JkIGtoJm9jaXJjO25nIMSRxrDhu6NjIMSR4buDIHRy4buRbmcmcXVvdDspCiAgICAgICAgQFRvU3RyaW5nLkV4Y2x1ZGUKICAgICAgICBwcml2YXRlIFN0cmluZyBwYXNzd29yZDsKICAgIH0KCiAgICBARGF0YQogICAgQEFsbEFyZ3NDb25zdHJ1Y3RvcgogICAgcHVibGljIHN0YXRpYyBjbGFzcyBSZXNwb25zZSB7CiAgICAgICAgcHJpdmF0ZSBTdHJpbmcgdG9rZW47CiAgICAgICAgcHJpdmF0ZSBTdHJpbmcgdXNlcm5hbWU7CiAgICB9Cn0KCmNsYXNzIEdhbWVEdG8gewogICAgQERhdGEKICAgIHB1YmxpYyBzdGF0aWMgY2xhc3MgR3Vlc3NSZXF1ZXN0IHsKICAgICAgICBATWluKHZhbHVlID0gMSwgbWVzc2FnZSA9ICZxdW90O1Phu5EgxJFvJmFhY3V0ZTtuIG7hurFtIHRyb25nIGtob+G6o25nIDEtNSZxdW90OykKICAgICAgICBATWF4KHZhbHVlID0gNSwgbWVzc2FnZSA9ICZxdW90O1Phu5EgxJFvJmFhY3V0ZTtuIG7hurFtIHRyb25nIGtob+G6o25nIDEtNSZxdW90OykKICAgICAgICBwcml2YXRlIGludCBudW1iZXI7CiAgICB9CgogICAgQERhdGEKICAgIEBBbGxBcmdzQ29uc3RydWN0b3IKICAgIEBOb0FyZ3NDb25zdHJ1Y3RvcgogICAgQEJ1aWxkZXIKICAgIHB1YmxpYyBzdGF0aWMgY2xhc3MgR3Vlc3NSZXNwb25zZSB7CiAgICAgICAgQEpzb25Qcm9wZXJ0eSgmcXVvdDtpc1dpbiZxdW90OykKICAgICAgICBwcml2YXRlIGJvb2xlYW4gaXNXaW47CgogICAgICAgIHByaXZhdGUgaW50IHVzZXJHdWVzczsKICAgICAgICBwcml2YXRlIGludCBzZXJ2ZXJOdW1iZXI7CiAgICAgICAgcHJpdmF0ZSBpbnQgcmVtYWluaW5nVHVybnM7CiAgICAgICAgcHJpdmF0ZSBpbnQgY3VycmVudFNjb3JlOwogICAgICAgIHByaXZhdGUgU3RyaW5nIG1lc3NhZ2U7CiAgICB9CgogICAgQERhdGEKICAgIEBBbGxBcmdzQ29uc3RydWN0b3IKICAgIEBCdWlsZGVyCiAgICBwdWJsaWMgc3RhdGljIGNsYXNzIFVzZXJQcm9maWxlUmVzcG9uc2UgewogICAgICAgIHByaXZhdGUgU3RyaW5nIHVzZXJuYW1lOwogICAgICAgIHByaXZhdGUgaW50IHNjb3JlOwogICAgICAgIHByaXZhdGUgaW50IHR1cm5zOwogICAgfQoKICAgIEBEYXRhCiAgICBAQWxsQXJnc0NvbnN0cnVjdG9yCiAgICBwdWJsaWMgc3RhdGljIGNsYXNzIExlYWRlcmJvYXJkUmVzcG9uc2UgewogICAgICAgIHByaXZhdGUgU3RyaW5nIHVzZXJuYW1lOwogICAgICAgIHByaXZhdGUgaW50IHNjb3JlOwogICAgfQp9CgovLyA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0KLy8gNS4gU0VDVVJJVFkgJmFtcDsgSldUCi8vID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQpAQ29tcG9uZW50CmNsYXNzIEp3dFRva2VuUHJvdmlkZXIgewoKICAgIHByaXZhdGUgZmluYWwgS2V5IGtleTsKICAgIHByaXZhdGUgZmluYWwgbG9uZyBleHBpcmF0aW9uTXM7CgogICAgcHVibGljIEp3dFRva2VuUHJvdmlkZXIoCiAgICAgICAgICAgIEBWYWx1ZSgmcXVvdDske2p3dC5zZWNyZXQ6OWE2ZThmMTJiNDVjNzhkOTBlMWYyYTNiNGM1ZDZlN2Y4YTliMGMxZDJlM2Y0YTViNmM3ZDhlOWYwYTFiMmMzZH0mcXVvdDspIFN0cmluZyBzZWNyZXQsCiAgICAgICAgICAgIEBWYWx1ZSgmcXVvdDske2p3dC5leHBpcmF0aW9uLW1zOjg2NDAwMDAwfSZxdW90OykgbG9uZyBleHBpcmF0aW9uTXMpIHsKICAgICAgICB0aGlzLmtleSA9IEtleXMuaG1hY1NoYUtleUZvcihzZWNyZXQuZ2V0Qnl0ZXMoU3RhbmRhcmRDaGFyc2V0cy5VVEZfOCkpOwogICAgICAgIHRoaXMuZXhwaXJhdGlvbk1zID0gZXhwaXJhdGlvbk1zOwogICAgfQoKICAgIHB1YmxpYyBTdHJpbmcgZ2VuZXJhdGVUb2tlbihTdHJpbmcgdXNlcm5hbWUpIHsKICAgICAgICBEYXRlIG5vdyA9IG5ldyBEYXRlKCk7CiAgICAgICAgRGF0ZSBleHBpcnlEYXRlID0gbmV3IERhdGUobm93LmdldFRpbWUoKSArIGV4cGlyYXRpb25Ncyk7CgogICAgICAgIHJldHVybiBKd3RzLmJ1aWxkZXIoKQogICAgICAgICAgICAgICAgLnNldFN1YmplY3QodXNlcm5hbWUpCiAgICAgICAgICAgICAgICAuc2V0SXNzdWVkQXQobm93KQogICAgICAgICAgICAgICAgLnNldEV4cGlyYXRpb24oZXhwaXJ5RGF0ZSkKICAgICAgICAgICAgICAgIC5zaWduV2l0aChrZXksIFNpZ25hdHVyZUFsZ29yaXRobS5IUzI1NikKICAgICAgICAgICAgICAgIC5jb21wYWN0KCk7CiAgICB9CgogICAgcHVibGljIFN0cmluZyBnZXRVc2VybmFtZUZyb21Ub2tlbihTdHJpbmcgdG9rZW4pIHsKICAgICAgICBDbGFpbXMgY2xhaW1zID0gSnd0cy5wYXJzZXJCdWlsZGVyKCkKICAgICAgICAgICAgICAgIC5zZXRTaWduaW5nS2V5KGtleSkKICAgICAgICAgICAgICAgIC5idWlsZCgpCiAgICAgICAgICAgICAgICAucGFyc2VDbGFpbXNKd3ModG9rZW4pCiAgICAgICAgICAgICAgICAuZ2V0Qm9keSgpOwogICAgICAgIHJldHVybiBjbGFpbXMuZ2V0U3ViamVjdCgpOwogICAgfQoKICAgIHB1YmxpYyBib29sZWFuIHZhbGlkYXRlVG9rZW4oU3RyaW5nIHRva2VuKSB7CiAgICAgICAgdHJ5IHsKICAgICAgICAgICAgSnd0cy5wYXJzZXJCdWlsZGVyKCkuc2V0U2lnbmluZ0tleShrZXkpLmJ1aWxkKCkucGFyc2VDbGFpbXNKd3ModG9rZW4pOwogICAgICAgICAgICByZXR1cm4gdHJ1ZTsKICAgICAgICB9IGNhdGNoIChKd3RFeGNlcHRpb24gfCBJbGxlZ2FsQXJndW1lbnRFeGNlcHRpb24gZSkgewogICAgICAgICAgICByZXR1cm4gZmFsc2U7CiAgICAgICAgfQogICAgfQp9CgpAQ29tcG9uZW50CmNsYXNzIEp3dEZpbHRlciBleHRlbmRzIG9yZy5zcHJpbmdmcmFtZXdvcmsud2ViLmZpbHRlci5PbmNlUGVyUmVxdWVzdEZpbHRlciB7CgogICAgcHJpdmF0ZSBmaW5hbCBKd3RUb2tlblByb3ZpZGVyIHRva2VuUHJvdmlkZXI7CgogICAgcHVibGljIEp3dEZpbHRlcihKd3RUb2tlblByb3ZpZGVyIHRva2VuUHJvdmlkZXIpIHsKICAgICAgICB0aGlzLnRva2VuUHJvdmlkZXIgPSB0b2tlblByb3ZpZGVyOwogICAgfQoKICAgIEBPdmVycmlkZQogICAgcHJvdGVjdGVkIHZvaWQgZG9GaWx0ZXJJbnRlcm5hbChIdHRwU2VydmxldFJlcXVlc3QgcmVxdWVzdCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgSHR0cFNlcnZsZXRSZXNwb25zZSByZXNwb25zZSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgRmlsdGVyQ2hhaW4gZmlsdGVyQ2hhaW4pCiAgICAgICAgICAgIHRocm93cyBTZXJ2bGV0RXhjZXB0aW9uLCBJT0V4Y2VwdGlvbiB7CgogICAgICAgIFN0cmluZyBiZWFyZXJUb2tlbiA9IHJlcXVlc3QuZ2V0SGVhZGVyKCZxdW90O0F1dGhvcml6YXRpb24mcXVvdDspOwogICAgICAgIFN0cmluZyB0b2tlbiA9IG51bGw7CgogICAgICAgIGlmIChTdHJpbmdVdGlscy5oYXNUZXh0KGJlYXJlclRva2VuKSAmYW1wOyZhbXA7IGJlYXJlclRva2VuLnN0YXJ0c1dpdGgoJnF1b3Q7QmVhcmVyICZxdW90OykpIHsKICAgICAgICAgICAgdG9rZW4gPSBiZWFyZXJUb2tlbi5zdWJzdHJpbmcoNyk7CiAgICAgICAgfQoKICAgICAgICBpZiAoU3RyaW5nVXRpbHMuaGFzVGV4dCh0b2tlbikpIHsKICAgICAgICAgICAgdHJ5IHsKICAgICAgICAgICAgICAgIFN0cmluZyB1c2VybmFtZSA9IHRva2VuUHJvdmlkZXIuZ2V0VXNlcm5hbWVGcm9tVG9rZW4odG9rZW4pOwogICAgICAgICAgICAgICAgVXNlcm5hbWVQYXNzd29yZEF1dGhlbnRpY2F0aW9uVG9rZW4gYXV0aCA9IG5ldyBVc2VybmFtZVBhc3N3b3JkQXV0aGVudGljYXRpb25Ub2tlbigKICAgICAgICAgICAgICAgICAgICAgICAgdXNlcm5hbWUsIG51bGwsIENvbGxlY3Rpb25zLmVtcHR5TGlzdCgpCiAgICAgICAgICAgICAgICApOwogICAgICAgICAgICAgICAgU2VjdXJpdHlDb250ZXh0SG9sZGVyLmdldENvbnRleHQoKS5zZXRBdXRoZW50aWNhdGlvbihhdXRoKTsKICAgICAgICAgICAgfSBjYXRjaCAoSnd0RXhjZXB0aW9uIHwgSWxsZWdhbEFyZ3VtZW50RXhjZXB0aW9uIGlnbm9yZWQpIHsKICAgICAgICAgICAgICAgIFNlY3VyaXR5Q29udGV4dEhvbGRlci5jbGVhckNvbnRleHQoKTsKICAgICAgICAgICAgfQogICAgICAgIH0KCiAgICAgICAgZmlsdGVyQ2hhaW4uZG9GaWx0ZXIocmVxdWVzdCwgcmVzcG9uc2UpOwogICAgfQp9CgpAQ29uZmlndXJhdGlvbgpARW5hYmxlV2ViU2VjdXJpdHkKY2xhc3MgU2VjdXJpdHlDb25maWcgewoKICAgIHByaXZhdGUgZmluYWwgSnd0RmlsdGVyIGp3dEZpbHRlcjsKCiAgICBwdWJsaWMgU2VjdXJpdHlDb25maWcoSnd0RmlsdGVyIGp3dEZpbHRlcikgewogICAgICAgIHRoaXMuand0RmlsdGVyID0gand0RmlsdGVyOwogICAgfQoKICAgIEBCZWFuCiAgICBwdWJsaWMgU2VjdXJpdHlGaWx0ZXJDaGFpbiBmaWx0ZXJDaGFpbihIdHRwU2VjdXJpdHkgaHR0cCkgdGhyb3dzIEV4Y2VwdGlvbiB7CiAgICAgICAgaHR0cAogICAgICAgICAgICAgICAgLmNzcmYoY3NyZiAtJmd0OyBjc3JmLmRpc2FibGUoKSkKICAgICAgICAgICAgICAgIC5zZXNzaW9uTWFuYWdlbWVudChzZXNzaW9uIC0mZ3Q7IHNlc3Npb24uc2Vzc2lvbkNyZWF0aW9uUG9saWN5KFNlc3Npb25DcmVhdGlvblBvbGljeS5TVEFURUxFU1MpKQogICAgICAgICAgICAgICAgLmF1dGhvcml6ZUh0dHBSZXF1ZXN0cyhhdXRoIC0mZ3Q7IGF1dGgKICAgICAgICAgICAgICAgICAgICAgICAgLnJlcXVlc3RNYXRjaGVycygmcXVvdDsvcmVnaXN0ZXImcXVvdDssICZxdW90Oy9sb2dpbiZxdW90OywgJnF1b3Q7L2gyLWNvbnNvbGUvKiomcXVvdDspLnBlcm1pdEFsbCgpCiAgICAgICAgICAgICAgICAgICAgICAgIC5hbnlSZXF1ZXN0KCkuYXV0aGVudGljYXRlZCgpCiAgICAgICAgICAgICAgICApCiAgICAgICAgICAgICAgICAuaGVhZGVycyhoZWFkZXJzIC0mZ3Q7IGhlYWRlcnMuZnJhbWVPcHRpb25zKGZyYW1lIC0mZ3Q7IGZyYW1lLmRpc2FibGUoKSkpOwoKICAgICAgICBodHRwLmFkZEZpbHRlckJlZm9yZShqd3RGaWx0ZXIsIFVzZXJuYW1lUGFzc3dvcmRBdXRoZW50aWNhdGlvbkZpbHRlci5jbGFzcyk7CgogICAgICAgIHJldHVybiBodHRwLmJ1aWxkKCk7CiAgICB9CgogICAgQEJlYW4KICAgIHB1YmxpYyBQYXNzd29yZEVuY29kZXIgcGFzc3dvcmRFbmNvZGVyKCkgewogICAgICAgIHJldHVybiBuZXcgQkNyeXB0UGFzc3dvcmRFbmNvZGVyKCk7CiAgICB9Cn0KCi8vID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQovLyA2LiBTRVJWSUNFIExBWUVSCi8vID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQpAU2VydmljZQpjbGFzcyBHYW1lU2VydmljZSB7CgogICAgcHJpdmF0ZSBmaW5hbCBVc2VyUmVwb3NpdG9yeSB1c2VyUmVwb3NpdG9yeTsKICAgIHByaXZhdGUgZmluYWwgUGFzc3dvcmRFbmNvZGVyIHBhc3N3b3JkRW5jb2RlcjsKICAgIHByaXZhdGUgZmluYWwgSnd0VG9rZW5Qcm92aWRlciB0b2tlblByb3ZpZGVyOwogICAgcHJpdmF0ZSBmaW5hbCBSYW5kb20gcmFuZG9tID0gbmV3IFJhbmRvbSgpOwoKICAgIHB1YmxpYyBHYW1lU2VydmljZShVc2VyUmVwb3NpdG9yeSB1c2VyUmVwb3NpdG9yeSwKICAgICAgICAgICAgICAgICAgICAgICBQYXNzd29yZEVuY29kZXIgcGFzc3dvcmRFbmNvZGVyLAogICAgICAgICAgICAgICAgICAgICAgIEp3dFRva2VuUHJvdmlkZXIgdG9rZW5Qcm92aWRlcikgewogICAgICAgIHRoaXMudXNlclJlcG9zaXRvcnkgPSB1c2VyUmVwb3NpdG9yeTsKICAgICAgICB0aGlzLnBhc3N3b3JkRW5jb2RlciA9IHBhc3N3b3JkRW5jb2RlcjsKICAgICAgICB0aGlzLnRva2VuUHJvdmlkZXIgPSB0b2tlblByb3ZpZGVyOwogICAgfQoKICAgIEBUcmFuc2FjdGlvbmFsCiAgICBAQ2FjaGVFdmljdCh2YWx1ZSA9ICZxdW90O2xlYWRlcmJvYXJkJnF1b3Q7LCBhbGxFbnRyaWVzID0gdHJ1ZSkKICAgIHB1YmxpYyBTdHJpbmcgcmVnaXN0ZXIoQXV0aER0by5SZXF1ZXN0IHJlcXVlc3QpIHsKICAgICAgICBpZiAodXNlclJlcG9zaXRvcnkuZmluZEJ5VXNlcm5hbWUocmVxdWVzdC5nZXRVc2VybmFtZSgpKS5pc1ByZXNlbnQoKSkgewogICAgICAgICAgICB0aHJvdyBuZXcgUnVudGltZUV4Y2VwdGlvbigmcXVvdDtUJmVjaXJjO24gxJHEg25nIG5o4bqtcCDEkSZhdGlsZGU7IHThu5NuIHThuqFpISZxdW90Oyk7CiAgICAgICAgfQoKICAgICAgICBVc2VyIHVzZXIgPSBVc2VyLmJ1aWxkZXIoKQogICAgICAgICAgICAgICAgLnVzZXJuYW1lKHJlcXVlc3QuZ2V0VXNlcm5hbWUoKSkKICAgICAgICAgICAgICAgIC5wYXNzd29yZChwYXNzd29yZEVuY29kZXIuZW5jb2RlKHJlcXVlc3QuZ2V0UGFzc3dvcmQoKSkpCiAgICAgICAgICAgICAgICAudHVybnMoNSkKICAgICAgICAgICAgICAgIC5zY29yZSgwKQogICAgICAgICAgICAgICAgLmJ1aWxkKCk7CgogICAgICAgIHVzZXJSZXBvc2l0b3J5LnNhdmUodXNlcik7CiAgICAgICAgcmV0dXJuICZxdW90O8SQxINuZyBrJnlhY3V0ZTsgdGgmYWdyYXZlO25oIGMmb2NpcmM7bmchJnF1b3Q7OwogICAgfQoKICAgIEBUcmFuc2FjdGlvbmFsKHJlYWRPbmx5ID0gdHJ1ZSkKICAgIHB1YmxpYyBBdXRoRHRvLlJlc3BvbnNlIGxvZ2luKEF1dGhEdG8uUmVxdWVzdCByZXF1ZXN0KSB7CiAgICAgICAgVXNlciB1c2VyID0gdXNlclJlcG9zaXRvcnkuZmluZEJ5VXNlcm5hbWUocmVxdWVzdC5nZXRVc2VybmFtZSgpKQogICAgICAgICAgICAgICAgLm9yRWxzZVRocm93KCgpIC0mZ3Q7IG5ldyBSdW50aW1lRXhjZXB0aW9uKCZxdW90O1NhaSB0JmFncmF2ZTtpIGtob+G6o24gaG/hurdjIG3huq10IGto4bqpdSZxdW90OykpOwoKICAgICAgICBpZiAoIXBhc3N3b3JkRW5jb2Rlci5tYXRjaGVzKHJlcXVlc3QuZ2V0UGFzc3dvcmQoKSwgdXNlci5nZXRQYXNzd29yZCgpKSkgewogICAgICAgICAgICB0aHJvdyBuZXcgUnVudGltZUV4Y2VwdGlvbigmcXVvdDtTYWkgdCZhZ3JhdmU7aSBraG/huqNuIGhv4bq3YyBt4bqtdCBraOG6qXUmcXVvdDspOwogICAgICAgIH0KCiAgICAgICAgU3RyaW5nIHRva2VuID0gdG9rZW5Qcm92aWRlci5nZW5lcmF0ZVRva2VuKHVzZXIuZ2V0VXNlcm5hbWUoKSk7CiAgICAgICAgcmV0dXJuIG5ldyBBdXRoRHRvLlJlc3BvbnNlKHRva2VuLCB1c2VyLmdldFVzZXJuYW1lKCkpOwogICAgfQoKICAgIEBUcmFuc2FjdGlvbmFsKHJlYWRPbmx5ID0gdHJ1ZSkKICAgIHB1YmxpYyBHYW1lRHRvLlVzZXJQcm9maWxlUmVzcG9uc2UgZ2V0UHJvZmlsZShTdHJpbmcgdXNlcm5hbWUpIHsKICAgICAgICBVc2VyIHVzZXIgPSB1c2VyUmVwb3NpdG9yeS5maW5kQnlVc2VybmFtZSh1c2VybmFtZSkKICAgICAgICAgICAgICAgIC5vckVsc2VUaHJvdygoKSAtJmd0OyBuZXcgUnVudGltZUV4Y2VwdGlvbigmcXVvdDtVc2VyIGtoJm9jaXJjO25nIHThu5NuIHThuqFpJnF1b3Q7KSk7CgogICAgICAgIHJldHVybiBHYW1lRHRvLlVzZXJQcm9maWxlUmVzcG9uc2UuYnVpbGRlcigpCiAgICAgICAgICAgICAgICAudXNlcm5hbWUodXNlci5nZXRVc2VybmFtZSgpKQogICAgICAgICAgICAgICAgLnNjb3JlKHVzZXIuZ2V0U2NvcmUoKSkKICAgICAgICAgICAgICAgIC50dXJucyh1c2VyLmdldFR1cm5zKCkpCiAgICAgICAgICAgICAgICAuYnVpbGQoKTsKICAgIH0KCiAgICBAVHJhbnNhY3Rpb25hbAogICAgQENhY2hlRXZpY3QodmFsdWUgPSAmcXVvdDtsZWFkZXJib2FyZCZxdW90OywgYWxsRW50cmllcyA9IHRydWUsCiAgICAgICAgICAgIGNvbmRpdGlvbiA9ICZxdW90OyNyZXN1bHQgIT0gbnVsbCAmYW1wOyZhbXA7ICNyZXN1bHQud2luJnF1b3Q7KQogICAgcHVibGljIEdhbWVEdG8uR3Vlc3NSZXNwb25zZSBndWVzc051bWJlcihTdHJpbmcgdXNlcm5hbWUsIGludCB1c2VyR3Vlc3MpIHsKICAgICAgICBpZiAodXNlckd1ZXNzICZsdDsgMSB8fCB1c2VyR3Vlc3MgJmd0OyA1KSB7CiAgICAgICAgICAgIHRocm93IG5ldyBJbGxlZ2FsQXJndW1lbnRFeGNlcHRpb24oJnF1b3Q7U+G7kSDEkW8mYWFjdXRlO24gcGjhuqNpIG7hurFtIHRyb25nIGtob+G6o25nIDEtNSZxdW90Oyk7CiAgICAgICAgfQoKICAgICAgICBVc2VyIHVzZXIgPSB1c2VyUmVwb3NpdG9yeS5maW5kQnlVc2VybmFtZVdpdGhMb2NrKHVzZXJuYW1lKQogICAgICAgICAgICAgICAgLm9yRWxzZVRocm93KCgpIC0mZ3Q7IG5ldyBSdW50aW1lRXhjZXB0aW9uKCZxdW90O1VzZXIga2gmb2NpcmM7bmcgdOG7k24gdOG6oWkmcXVvdDspKTsKCiAgICAgICAgaWYgKHVzZXIuZ2V0VHVybnMoKSAmbHQ7PSAwKSB7CiAgICAgICAgICAgIHRocm93IG5ldyBSdW50aW1lRXhjZXB0aW9uKCZxdW90O0LhuqFuIMSRJmF0aWxkZTsgaOG6v3QgbMaw4bujdCBjaMahaSEgVnVpIGwmb2dyYXZlO25nIG11YSB0aCZlY2lyYzttIGzGsOG7o3QuJnF1b3Q7KTsKICAgICAgICB9CgogICAgICAgIHVzZXIuc2V0VHVybnModXNlci5nZXRUdXJucygpIC0gMSk7CgogICAgICAgIGJvb2xlYW4gaXNXaW4gPSByYW5kb20ubmV4dEludCgxMDApICZsdDsgNTsKICAgICAgICBpbnQgc2VydmVyTnVtYmVyOwoKICAgICAgICBpZiAoaXNXaW4pIHsKICAgICAgICAgICAgc2VydmVyTnVtYmVyID0gdXNlckd1ZXNzOwogICAgICAgICAgICB1c2VyLnNldFNjb3JlKHVzZXIuZ2V0U2NvcmUoKSArIDEpOwogICAgICAgIH0gZWxzZSB7CiAgICAgICAgICAgIGRvIHsKICAgICAgICAgICAgICAgIHNlcnZlck51bWJlciA9IHJhbmRvbS5uZXh0SW50KDUpICsgMTsKICAgICAgICAgICAgfSB3aGlsZSAoc2VydmVyTnVtYmVyID09IHVzZXJHdWVzcyk7CiAgICAgICAgfQoKICAgICAgICAvLyBObyBleHBsaWNpdCBzYXZlIG5lZWRlZDogZW50aXR5IGlzIG1hbmFnZWQgYW5kIGRpcnR5LWNoZWNrZWQgaW5zaWRlIEBUcmFuc2FjdGlvbmFsLgoKICAgICAgICByZXR1cm4gR2FtZUR0by5HdWVzc1Jlc3BvbnNlLmJ1aWxkZXIoKQogICAgICAgICAgICAgICAgLmlzV2luKGlzV2luKQogICAgICAgICAgICAgICAgLnVzZXJHdWVzcyh1c2VyR3Vlc3MpCiAgICAgICAgICAgICAgICAuc2VydmVyTnVtYmVyKHNlcnZlck51bWJlcikKICAgICAgICAgICAgICAgIC5yZW1haW5pbmdUdXJucyh1c2VyLmdldFR1cm5zKCkpCiAgICAgICAgICAgICAgICAuY3VycmVudFNjb3JlKHVzZXIuZ2V0U2NvcmUoKSkKICAgICAgICAgICAgICAgIC5tZXNzYWdlKGlzV2luCiAgICAgICAgICAgICAgICAgICAgICAgID8gJnF1b3Q7Q2gmdWFjdXRlO2MgbeG7q25nISBC4bqhbiDEkSZhdGlsZGU7IMSRbyZhYWN1dGU7biDEkSZ1YWN1dGU7bmcgKCsxIMSRaeG7g20pJnF1b3Q7CiAgICAgICAgICAgICAgICAgICAgICAgIDogJnF1b3Q7UuG6pXQgdGnhur9jISBDaCZ1YWN1dGU7YyBi4bqhbiBtYXkgbeG6r24gbOG6p24gc2F1LiZxdW90OykKICAgICAgICAgICAgICAgIC5idWlsZCgpOwogICAgfQoKICAgIEBUcmFuc2FjdGlvbmFsCiAgICBwdWJsaWMgR2FtZUR0by5Vc2VyUHJvZmlsZVJlc3BvbnNlIGJ1eVR1cm5zKFN0cmluZyB1c2VybmFtZSwgaW50IGFkZGl0aW9uYWxUdXJucykgewogICAgICAgIGlmIChhZGRpdGlvbmFsVHVybnMgJmx0Oz0gMCkgewogICAgICAgICAgICB0aHJvdyBuZXcgSWxsZWdhbEFyZ3VtZW50RXhjZXB0aW9uKCZxdW90O1Phu5EgbMaw4bujdCBtdWEgcGjhuqNpIGzhu5tuIGjGoW4gMCZxdW90Oyk7CiAgICAgICAgfQoKICAgICAgICBVc2VyIHVzZXIgPSB1c2VyUmVwb3NpdG9yeS5maW5kQnlVc2VybmFtZVdpdGhMb2NrKHVzZXJuYW1lKQogICAgICAgICAgICAgICAgLm9yRWxzZVRocm93KCgpIC0mZ3Q7IG5ldyBSdW50aW1lRXhjZXB0aW9uKCZxdW90O1VzZXIga2gmb2NpcmM7bmcgdOG7k24gdOG6oWkmcXVvdDspKTsKCiAgICAgICAgdXNlci5zZXRUdXJucyh1c2VyLmdldFR1cm5zKCkgKyBhZGRpdGlvbmFsVHVybnMpOwoKICAgICAgICByZXR1cm4gR2FtZUR0by5Vc2VyUHJvZmlsZVJlc3BvbnNlLmJ1aWxkZXIoKQogICAgICAgICAgICAgICAgLnVzZXJuYW1lKHVzZXIuZ2V0VXNlcm5hbWUoKSkKICAgICAgICAgICAgICAgIC5zY29yZSh1c2VyLmdldFNjb3JlKCkpCiAgICAgICAgICAgICAgICAudHVybnModXNlci5nZXRUdXJucygpKQogICAgICAgICAgICAgICAgLmJ1aWxkKCk7CiAgICB9CgogICAgQFRyYW5zYWN0aW9uYWwocmVhZE9ubHkgPSB0cnVlKQogICAgQENhY2hlYWJsZSh2YWx1ZSA9ICZxdW90O2xlYWRlcmJvYXJkJnF1b3Q7KQogICAgcHVibGljIExpc3QmbHQ7R2FtZUR0by5MZWFkZXJib2FyZFJlc3BvbnNlJmd0OyBnZXRMZWFkZXJib2FyZCgpIHsKICAgICAgICByZXR1cm4gdXNlclJlcG9zaXRvcnkuZmluZFRvcDEwQnlPcmRlckJ5U2NvcmVEZXNjKCkuc3RyZWFtKCkKICAgICAgICAgICAgICAgIC5tYXAodSAtJmd0OyBuZXcgR2FtZUR0by5MZWFkZXJib2FyZFJlc3BvbnNlKHUuZ2V0VXNlcm5hbWUoKSwgdS5nZXRTY29yZSgpKSkKICAgICAgICAgICAgICAgIC5jb2xsZWN0KENvbGxlY3RvcnMudG9MaXN0KCkpOwogICAgfQp9CgovLyA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0KLy8gNy4gQ09OVFJPTExFUgovLyA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0KQFJlc3RDb250cm9sbGVyCkBSZXF1ZXN0TWFwcGluZwpjbGFzcyBHYW1lQ29udHJvbGxlciB7CgogICAgcHJpdmF0ZSBmaW5hbCBHYW1lU2VydmljZSBnYW1lU2VydmljZTsKCiAgICBwdWJsaWMgR2FtZUNvbnRyb2xsZXIoR2FtZVNlcnZpY2UgZ2FtZVNlcnZpY2UpIHsKICAgICAgICB0aGlzLmdhbWVTZXJ2aWNlID0gZ2FtZVNlcnZpY2U7CiAgICB9CgogICAgQFBvc3RNYXBwaW5nKCZxdW90Oy9yZWdpc3RlciZxdW90OykKICAgIHB1YmxpYyBSZXNwb25zZUVudGl0eSZsdDtTdHJpbmcmZ3Q7IHJlZ2lzdGVyKEBWYWxpZCBAUmVxdWVzdEJvZHkgQXV0aER0by5SZXF1ZXN0IHJlcXVlc3QpIHsKICAgICAgICByZXR1cm4gUmVzcG9uc2VFbnRpdHkub2soZ2FtZVNlcnZpY2UucmVnaXN0ZXIocmVxdWVzdCkpOwogICAgfQoKICAgIEBQb3N0TWFwcGluZygmcXVvdDsvbG9naW4mcXVvdDspCiAgICBwdWJsaWMgUmVzcG9uc2VFbnRpdHkmbHQ7QXV0aER0by5SZXNwb25zZSZndDsgbG9naW4oQFZhbGlkIEBSZXF1ZXN0Qm9keSBBdXRoRHRvLlJlcXVlc3QgcmVxdWVzdCkgewogICAgICAgIHJldHVybiBSZXNwb25zZUVudGl0eS5vayhnYW1lU2VydmljZS5sb2dpbihyZXF1ZXN0KSk7CiAgICB9CgogICAgQEdldE1hcHBpbmcoJnF1b3Q7L21lJnF1b3Q7KQogICAgcHVibGljIFJlc3BvbnNlRW50aXR5Jmx0O0dhbWVEdG8uVXNlclByb2ZpbGVSZXNwb25zZSZndDsgZ2V0UHJvZmlsZShBdXRoZW50aWNhdGlvbiBhdXRoZW50aWNhdGlvbikgewogICAgICAgIHJldHVybiBSZXNwb25zZUVudGl0eS5vayhnYW1lU2VydmljZS5nZXRQcm9maWxlKGF1dGhlbnRpY2F0aW9uLmdldE5hbWUoKSkpOwogICAgfQoKICAgIEBQb3N0TWFwcGluZygmcXVvdDsvZ3Vlc3MmcXVvdDspCiAgICBwdWJsaWMgUmVzcG9uc2VFbnRpdHkmbHQ7R2FtZUR0by5HdWVzc1Jlc3BvbnNlJmd0OyBndWVzcygKICAgICAgICAgICAgQXV0aGVudGljYXRpb24gYXV0aGVudGljYXRpb24sCiAgICAgICAgICAgIEBWYWxpZCBAUmVxdWVzdEJvZHkgR2FtZUR0by5HdWVzc1JlcXVlc3QgcmVxdWVzdCkgewogICAgICAgIHJldHVybiBSZXNwb25zZUVudGl0eS5vayhnYW1lU2VydmljZS5ndWVzc051bWJlcihhdXRoZW50aWNhdGlvbi5nZXROYW1lKCksIHJlcXVlc3QuZ2V0TnVtYmVyKCkpKTsKICAgIH0KCiAgICBAUG9zdE1hcHBpbmcoJnF1b3Q7L2J1eS10dXJucyZxdW90OykKICAgIHB1YmxpYyBSZXNwb25zZUVudGl0eSZsdDtHYW1lRHRvLlVzZXJQcm9maWxlUmVzcG9uc2UmZ3Q7IGJ1eVR1cm5zKEF1dGhlbnRpY2F0aW9uIGF1dGhlbnRpY2F0aW9uKSB7CiAgICAgICAgcmV0dXJuIFJlc3BvbnNlRW50aXR5Lm9rKGdhbWVTZXJ2aWNlLmJ1eVR1cm5zKGF1dGhlbnRpY2F0aW9uLmdldE5hbWUoKSwgNSkpOwogICAgfQoKICAgIEBHZXRNYXBwaW5nKCZxdW90Oy9sZWFkZXJib2FyZCZxdW90OykKICAgIHB1YmxpYyBSZXNwb25zZUVudGl0eSZsdDtMaXN0Jmx0O0dhbWVEdG8uTGVhZGVyYm9hcmRSZXNwb25zZSZndDsmZ3Q7IGdldExlYWRlcmJvYXJkKCkgewogICAgICAgIHJldHVybiBSZXNwb25zZUVudGl0eS5vayhnYW1lU2VydmljZS5nZXRMZWFkZXJib2FyZCgpKTsKICAgIH0KfQoKLy8gPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09Ci8vIDguIEdMT0JBTCBFWENFUFRJT04gSEFORExFUgovLyA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0KQFJlc3RDb250cm9sbGVyQWR2aWNlCmNsYXNzIEdsb2JhbEV4Y2VwdGlvbkhhbmRsZXIgewoKICAgIEBFeGNlcHRpb25IYW5kbGVyKE1ldGhvZEFyZ3VtZW50Tm90VmFsaWRFeGNlcHRpb24uY2xhc3MpCiAgICBwdWJsaWMgUmVzcG9uc2VFbnRpdHkmbHQ7TWFwJmx0O1N0cmluZywgU3RyaW5nJmd0OyZndDsgaGFuZGxlVmFsaWRhdGlvbihNZXRob2RBcmd1bWVudE5vdFZhbGlkRXhjZXB0aW9uIGV4KSB7CiAgICAgICAgU3RyaW5nIG1lc3NhZ2UgPSBleC5nZXRCaW5kaW5nUmVzdWx0KCkuZ2V0RmllbGRFcnJvcnMoKS5zdHJlYW0oKQogICAgICAgICAgICAgICAgLm1hcChlcnIgLSZndDsgZXJyLmdldEZpZWxkKCkgKyAmcXVvdDs6ICZxdW90OyArIGVyci5nZXREZWZhdWx0TWVzc2FnZSgpKQogICAgICAgICAgICAgICAgLmNvbGxlY3QoQ29sbGVjdG9ycy5qb2luaW5nKCZxdW90OywgJnF1b3Q7KSk7CiAgICAgICAgcmV0dXJuIFJlc3BvbnNlRW50aXR5LmJhZFJlcXVlc3QoKS5ib2R5KENvbGxlY3Rpb25zLnNpbmdsZXRvbk1hcCgmcXVvdDtlcnJvciZxdW90OywgbWVzc2FnZSkpOwogICAgfQoKICAgIEBFeGNlcHRpb25IYW5kbGVyKElsbGVnYWxBcmd1bWVudEV4Y2VwdGlvbi5jbGFzcykKICAgIHB1YmxpYyBSZXNwb25zZUVudGl0eSZsdDtNYXAmbHQ7U3RyaW5nLCBTdHJpbmcmZ3Q7Jmd0OyBoYW5kbGVJbGxlZ2FsQXJndW1lbnQoSWxsZWdhbEFyZ3VtZW50RXhjZXB0aW9uIGV4KSB7CiAgICAgICAgcmV0dXJuIFJlc3BvbnNlRW50aXR5LmJhZFJlcXVlc3QoKS5ib2R5KENvbGxlY3Rpb25zLnNpbmdsZXRvbk1hcCgmcXVvdDtlcnJvciZxdW90OywgZXguZ2V0TWVzc2FnZSgpKSk7CiAgICB9CgogICAgQEV4Y2VwdGlvbkhhbmRsZXIoUnVudGltZUV4Y2VwdGlvbi5jbGFzcykKICAgIHB1YmxpYyBSZXNwb25zZUVudGl0eSZsdDtNYXAmbHQ7U3RyaW5nLCBTdHJpbmcmZ3Q7Jmd0OyBoYW5kbGVSdW50aW1lKFJ1bnRpbWVFeGNlcHRpb24gZXgpIHsKICAgICAgICByZXR1cm4gUmVzcG9uc2VFbnRpdHkuc3RhdHVzKEh0dHBTdGF0dXMuQkFEX1JFUVVFU1QpCiAgICAgICAgICAgICAgICAuYm9keShDb2xsZWN0aW9ucy5zaW5nbGV0b25NYXAoJnF1b3Q7ZXJyb3ImcXVvdDssIGV4LmdldE1lc3NhZ2UoKSkpOwogICAgfQp9
package com.inmobi.game;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import jakarta.persistence.*;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import lombok.*;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Lock;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.*;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.Key;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.stream.Collectors;
// ==========================================
// 1. MAIN APPLICATION
// ==========================================
@SpringBootApplication
@EnableCaching
class GameApplication {
public static void main(String[] args) {
SpringApplication.run(GameApplication.class, args);
}
}
// ==========================================
// 2. DOMAIN ENTITY
// ==========================================
@Entity
@Table(name = "users", indexes = {
@Index(name = "idx_user_score", columnList = "score")
})
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(unique = true, nullable = false)
private String username;
@Column(nullable = false)
private String password;
@Builder.Default
@Column(nullable = false)
private Integer score = 0;
@Builder.Default
@Column(nullable = false)
private Integer turns = 0;
}
// ==========================================
// 3. REPOSITORY
// ==========================================
interface UserRepository extends JpaRepository<User, Long> {
java.util.Optional<User> findByUsername(String username);
// Lock by username directly to avoid stale entity loaded by a previous non-locking query.
@Lock(LockModeType.PESSIMISTIC_WRITE)
@Query("SELECT u FROM User u WHERE u.username = :username")
java.util.Optional<User> findByUsernameWithLock(@Param("username") String username);
List<User> findTop10ByOrderByScoreDesc();
}
// ==========================================
// 4. DTOs
// ==========================================
class AuthDto {
@Data
public static class Request {
@NotBlank(message = "Username không được để trống")
private String username;
@NotBlank(message = "Password không được để trống")
@ToString.Exclude
private String password;
}
@Data
@AllArgsConstructor
public static class Response {
private String token;
private String username;
}
}
class GameDto {
@Data
public static class GuessRequest {
@Min(value = 1, message = "Số đoán nằm trong khoảng 1-5")
@Max(value = 5, message = "Số đoán nằm trong khoảng 1-5")
private int number;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public static class GuessResponse {
@JsonProperty("isWin")
private boolean isWin;
private int userGuess;
private int serverNumber;
private int remainingTurns;
private int currentScore;
private String message;
}
@Data
@AllArgsConstructor
@Builder
public static class UserProfileResponse {
private String username;
private int score;
private int turns;
}
@Data
@AllArgsConstructor
public static class LeaderboardResponse {
private String username;
private int score;
}
}
// ==========================================
// 5. SECURITY & JWT
// ==========================================
@Component
class JwtTokenProvider {
private final Key key;
private final long expirationMs;
public JwtTokenProvider(
@Value("${jwt.secret:9a6e8f12b45c78d90e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d}") String secret,
@Value("${jwt.expiration-ms:86400000}") long expirationMs) {
this.key = Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8));
this.expirationMs = expirationMs;
}
public String generateToken(String username) {
Date now = new Date();
Date expiryDate = new Date(now.getTime() + expirationMs);
return Jwts.builder()
.setSubject(username)
.setIssuedAt(now)
.setExpiration(expiryDate)
.signWith(key, SignatureAlgorithm.HS256)
.compact();
}
public String getUsernameFromToken(String token) {
Claims claims = Jwts.parserBuilder()
.setSigningKey(key)
.build()
.parseClaimsJws(token)
.getBody();
return claims.getSubject();
}
public boolean validateToken(String token) {
try {
Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token);
return true;
} catch (JwtException | IllegalArgumentException e) {
return false;
}
}
}
@Component
class JwtFilter extends org.springframework.web.filter.OncePerRequestFilter {
private final JwtTokenProvider tokenProvider;
public JwtFilter(JwtTokenProvider tokenProvider) {
this.tokenProvider = tokenProvider;
}
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain)
throws ServletException, IOException {
String bearerToken = request.getHeader("Authorization");
String token = null;
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
token = bearerToken.substring(7);
}
if (StringUtils.hasText(token)) {
try {
String username = tokenProvider.getUsernameFromToken(token);
UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(
username, null, Collections.emptyList()
);
SecurityContextHolder.getContext().setAuthentication(auth);
} catch (JwtException | IllegalArgumentException ignored) {
SecurityContextHolder.clearContext();
}
}
filterChain.doFilter(request, response);
}
}
@Configuration
@EnableWebSecurity
class SecurityConfig {
private final JwtFilter jwtFilter;
public SecurityConfig(JwtFilter jwtFilter) {
this.jwtFilter = jwtFilter;
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable())
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(auth -> auth
.requestMatchers("/register", "/login", "/h2-console/**").permitAll()
.anyRequest().authenticated()
)
.headers(headers -> headers.frameOptions(frame -> frame.disable()));
http.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
// ==========================================
// 6. SERVICE LAYER
// ==========================================
@Service
class GameService {
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
private final JwtTokenProvider tokenProvider;
private final Random random = new Random();
public GameService(UserRepository userRepository,
PasswordEncoder passwordEncoder,
JwtTokenProvider tokenProvider) {
this.userRepository = userRepository;
this.passwordEncoder = passwordEncoder;
this.tokenProvider = tokenProvider;
}
@Transactional
@CacheEvict(value = "leaderboard", allEntries = true)
public String register(AuthDto.Request request) {
if (userRepository.findByUsername(request.getUsername()).isPresent()) {
throw new RuntimeException("Tên đăng nhập đã tồn tại!");
}
User user = User.builder()
.username(request.getUsername())
.password(passwordEncoder.encode(request.getPassword()))
.turns(5)
.score(0)
.build();
userRepository.save(user);
return "Đăng ký thành công!";
}
@Transactional(readOnly = true)
public AuthDto.Response login(AuthDto.Request request) {
User user = userRepository.findByUsername(request.getUsername())
.orElseThrow(() -> new RuntimeException("Sai tài khoản hoặc mật khẩu"));
if (!passwordEncoder.matches(request.getPassword(), user.getPassword())) {
throw new RuntimeException("Sai tài khoản hoặc mật khẩu");
}
String token = tokenProvider.generateToken(user.getUsername());
return new AuthDto.Response(token, user.getUsername());
}
@Transactional(readOnly = true)
public GameDto.UserProfileResponse getProfile(String username) {
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new RuntimeException("User không tồn tại"));
return GameDto.UserProfileResponse.builder()
.username(user.getUsername())
.score(user.getScore())
.turns(user.getTurns())
.build();
}
@Transactional
@CacheEvict(value = "leaderboard", allEntries = true,
condition = "#result != null && #result.win")
public GameDto.GuessResponse guessNumber(String username, int userGuess) {
if (userGuess < 1 || userGuess > 5) {
throw new IllegalArgumentException("Số đoán phải nằm trong khoảng 1-5");
}
User user = userRepository.findByUsernameWithLock(username)
.orElseThrow(() -> new RuntimeException("User không tồn tại"));
if (user.getTurns() <= 0) {
throw new RuntimeException("Bạn đã hết lượt chơi! Vui lòng mua thêm lượt.");
}
user.setTurns(user.getTurns() - 1);
boolean isWin = random.nextInt(100) < 5;
int serverNumber;
if (isWin) {
serverNumber = userGuess;
user.setScore(user.getScore() + 1);
} else {
do {
serverNumber = random.nextInt(5) + 1;
} while (serverNumber == userGuess);
}
// No explicit save needed: entity is managed and dirty-checked inside @Transactional.
return GameDto.GuessResponse.builder()
.isWin(isWin)
.userGuess(userGuess)
.serverNumber(serverNumber)
.remainingTurns(user.getTurns())
.currentScore(user.getScore())
.message(isWin
? "Chúc mừng! Bạn đã đoán đúng (+1 điểm)"
: "Rất tiếc! Chúc bạn may mắn lần sau.")
.build();
}
@Transactional
public GameDto.UserProfileResponse buyTurns(String username, int additionalTurns) {
if (additionalTurns <= 0) {
throw new IllegalArgumentException("Số lượt mua phải lớn hơn 0");
}
User user = userRepository.findByUsernameWithLock(username)
.orElseThrow(() -> new RuntimeException("User không tồn tại"));
user.setTurns(user.getTurns() + additionalTurns);
return GameDto.UserProfileResponse.builder()
.username(user.getUsername())
.score(user.getScore())
.turns(user.getTurns())
.build();
}
@Transactional(readOnly = true)
@Cacheable(value = "leaderboard")
public List<GameDto.LeaderboardResponse> getLeaderboard() {
return userRepository.findTop10ByOrderByScoreDesc().stream()
.map(u -> new GameDto.LeaderboardResponse(u.getUsername(), u.getScore()))
.collect(Collectors.toList());
}
}
// ==========================================
// 7. CONTROLLER
// ==========================================
@RestController
@RequestMapping
class GameController {
private final GameService gameService;
public GameController(GameService gameService) {
this.gameService = gameService;
}
@PostMapping("/register")
public ResponseEntity<String> register(@Valid @RequestBody AuthDto.Request request) {
return ResponseEntity.ok(gameService.register(request));
}
@PostMapping("/login")
public ResponseEntity<AuthDto.Response> login(@Valid @RequestBody AuthDto.Request request) {
return ResponseEntity.ok(gameService.login(request));
}
@GetMapping("/me")
public ResponseEntity<GameDto.UserProfileResponse> getProfile(Authentication authentication) {
return ResponseEntity.ok(gameService.getProfile(authentication.getName()));
}
@PostMapping("/guess")
public ResponseEntity<GameDto.GuessResponse> guess(
Authentication authentication,
@Valid @RequestBody GameDto.GuessRequest request) {
return ResponseEntity.ok(gameService.guessNumber(authentication.getName(), request.getNumber()));
}
@PostMapping("/buy-turns")
public ResponseEntity<GameDto.UserProfileResponse> buyTurns(Authentication authentication) {
return ResponseEntity.ok(gameService.buyTurns(authentication.getName(), 5));
}
@GetMapping("/leaderboard")
public ResponseEntity<List<GameDto.LeaderboardResponse>> getLeaderboard() {
return ResponseEntity.ok(gameService.getLeaderboard());
}
}
// ==========================================
// 8. GLOBAL EXCEPTION HANDLER
// ==========================================
@RestControllerAdvice
class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<Map<String, String>> handleValidation(MethodArgumentNotValidException ex) {
String message = ex.getBindingResult().getFieldErrors().stream()
.map(err -> err.getField() + ": " + err.getDefaultMessage())
.collect(Collectors.joining(", "));
return ResponseEntity.badRequest().body(Collections.singletonMap("error", message));
}
@ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<Map<String, String>> handleIllegalArgument(IllegalArgumentException ex) {
return ResponseEntity.badRequest().body(Collections.singletonMap("error", ex.getMessage()));
}
@ExceptionHandler(RuntimeException.class)
public ResponseEntity<Map<String, String>> handleRuntime(RuntimeException ex) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body(Collections.singletonMap("error", ex.getMessage()));
}
}